Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Creating a C#/.NET CGI Executable


:P
On this page:

 Somebody asked a question today on the Universal Thread about creating a C# CGI executable for .NET. This seems kinda silly of course, since CGI is pretty much an outdated and slow technology superceded by more advanced application services and even newer low level APIs like ISAPI, NSAPI, Apache extensions etc. But nevertheless sometimes CGI is a quick and easy way to do something if you're working with a foreign environment and chances are that what you build as a CGI extension will run on any Web server.

 

The gentleman asking the question mentioned that he couldn’t get it to work so I quickly created a small program to see if there were any gotchas. It turns out it works fine.

 

Creating a CGI EXE is pretty easy of course. All you do is create a CONSOLE application. In this application the output you write to StdOut (ie. Console.Write() )  which becomes the output that gets written to the Web Server. You access Environment variables (ie. Environment.GetVariable()) to read any ServerVariables like QUERY_STRING, CONTENT_LENGTH etc. and you read from StdIn to retrieve any data posted to the application.

 

The following small program demonstrates all of this:

 

using System;

using System.IO;

using System.Collections;

using System.Text;

 

namespace CGITestApplication

{

      /// <summary>

      /// Summary description for Class1.

      /// </summary>

      class CGITest

      {

            /// <summary>

            /// The main entry point for the application.

            /// </summary>

            [STAThread]

            static void Main(string[] args)

            {

 

                  // *** Use this for debugging –

//      Hit the link then attach debugger to this process

//     and then pause to continue

                  // System.Threading.Thread.Sleep(30000);

                  // *** Loop through all the environement vars and write to string

                  IDictionary Dict = Environment.GetEnvironmentVariables();

                  StringBuilder sb = new System.Text.StringBuilder();

 

                  foreach(DictionaryEntry Item in Dict)

                  {

                        sb.Append((string) Item.Key + " - " + (string) Item.Value + "\r\n");

                  }

 

                  // *** Read individual values

                  string QueryString = Environment.GetEnvironmentVariable("QUERY_STRING");

 

 

                 

                  // *** Read all the incoming form data both text and binary

                  string FormData = "";

                  byte[] Data = null;

                  if (Environment.GetEnvironmentVariable("REQUEST_METHOD") == "POST")

                  {

                        Stream s = Console.OpenStandardInput();

                       

                        BinaryReader br = new BinaryReader(s);

                       

                        string Length =

 Environment.GetEnvironmentVariable("CONTENT_LENGTH");

                        int Size = Int32.Parse( Length);

                        Data = new byte[Size];

                        br.Read(Data,0,Size);

                        // *** don’t close the reader!

                       

                        FormData = System.Text.Encoding.Default.GetString(Data,0,Size);

                  }

 

                  Console.Write(

@"HTTP/1.1 200 OK

Content-type: text/html

 

<html>

Hello World

 

<pre>

<b>Environment and Server Variables:</b>

" + sb.ToString() + @"

 

<b>Form Vars (if any):</b>

" + FormData + @"

</pre>

</html>

");

                  }

      }

}

 

Couple of things of interest here in regards to retrieving the POST content. Note that I explicitly open the input stream here in order to retrieve the content as binary data. You’ll want to do this incase the data retrieved is something like a file upload or other binary content. If you just need default text you can use the Console.In TextReader to grab the content in the default encoding format. Make sure you don't close the Reader() or the request will fail.

 

To set this up with IIS you’ll need to do a few things:

 

  • Create a virtual directory (or use an existing one)
  • Make sure you enable Scripts and Executables if you’re
    going to access the EXE directly with a URL
  • In IIS 6 make sure you allow the EXE to be launched in
    the Web Extensions (otherwise you get a 404 error)
  • Stick the CGI executable into this directory

Debugging this executable called from IIS is a bit tricky too - you have to attach to the process when it's actually running. To debug I added a Thread.Sleep() to the handler code to allow enough time to attach a debugger to the process. Attach, then pause to break out of the Sleep. At that point you can step through the code. Note that you have to do this on every hit, since IIS launches the EXE and it then disappears, so you need to reattach on the next hit.

 

Now obviously CGI is pretty much legacy technology, and on IIS especially this is probably a waste of time since ASP.NET is so much easier and more optimized. However, for other Web Servers it might a be a quick and dirty way to get something up and running.

 

Realize also that CGI especially on IIS  all fairly slow (you can feel the lag time between hits), so I hardly recommend this. But if you’re using a non MS server and you must support CGI there it is.

 

If you are using a CGI executable I would highly recommend you hide that fact behind a script map extension rather than accessing the EXE directly. Move the EXE into some directory that's not Web accessible and then create a script map to it. So for example, on IIS you can assign the .CGITEST extension to the above EXE file and then access the EXE through any accesses to this script extension.

 

Alright, I MUST have better things to do with my time, eh? <g>

 


The Voices of Reason


 

Melle
December 06, 2004

# re: Creating a C#/.NET CGI Executable


Like, some non-MS Server but including the .NET Framework so that this CGI actually works, right? :-)

Rick Strahl
December 07, 2004

# re: Creating a C#/.NET CGI Executable

Well there is Mono I suppose <g>... Didn't say this was highly practical, but somebody asked the question and a quick search of Google turned up a number of related questions. So somebody's trying to do this <g>...

Garnet
September 03, 2005

# re: Creating a C#/.NET CGI Executable

Sorry, tried your example, but it doesn't work on Windows 2003 IIS 6.0. I created c:\inetpub\wwwroot\cgi-bin directory that I can run perl programs from, and regular win32 console apps also run from that directory if they are setup as web extensions.

But your example gives the following:

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'SimpleDotNetCGI_2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG)) File name: 'SimpleDotNetCGI_2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' ---> System.ArgumentException: Invalid directory on URL. at System.Security.Policy.PolicyLevel.GenericResolve(Evidence evidence, Boolean& allConst) at System.Security.Policy.PolicyLevel.Resolve(Evidence evidence, Int32 count, Char[] serializedEvidence) at System.Security.PolicyManager.ResolveHelper(Evidence evidence, PermissionSet request, Boolean systemPolicy) at System.Security.HostSecurityManager.ResolvePolicy(Evidence evidence) at System.Security.PolicyManager.Resolve(Evidence evidence, PermissionSet request) at System.Security.SecurityManager.ResolvePolicy(Evidence evidence, PermissionSet reqdPset, PermissionSet optPset, PermissionSet denyPset, PermissionSet& denied, Boolean checkExecutionPermission) at System.Security.SecurityManager.ResolvePolicy(Evidence evidence, PermissionSet reqdPset, PermissionSet optPset, PermissionSet denyPset, PermissionSet& denied, Int32& grantedIsUnrestricted, Boolean checkExecutionPermission)

Rick Strahl
September 03, 2005

# re: Creating a C#/.NET CGI Executable

Garnet,

One thing I forgot to mention is that you will need proper permissions to execute the EXE and any dependent assembly. It looks from your stacktrace that you're either missing a dependent assembly or don't have rights to load the dependency. The security environment will vary with your Web Server, as the server will load the EXE under a specific account (NETWORK SERVICE under IIS 6 I think).

Vlad
December 25, 2005

# THANKS!

Rick, I mean it. I spent two days banging my head againts the wall and trying to get how to read binary input. Now its clear. Thanks!

Too bad google only references your weblog on its 5 or 6th page...

Vlad
December 25, 2005

# re: Creating a C#/.NET CGI Executable

I noticed the following problem. I save the binary data received with your script. I do it with BinaryWriter. Somehow, the result and origin files contain different number of lines. Any ideas why it may be so?

Thanks,
Vlad

Themhz
June 15, 2006

# re: Creating a C#/.NET CGI Executable

Hello
i can see that its very easy to create something like this but i am trying to and i get the folowing error:

CGI Error
The specified CGI application misbehaved by not returning a complete set of HTTP headers. The headers it did return are:

Unhandled Exception: System.ArgumentException: Invalid directory on URL.
any suggestions?

I find this works if you compile using csc but not in sharpdevelop
September 27, 2006

# re: Creating a C#/.NET CGI Executable

any ideas?

Mikey
January 10, 2007

# re: Creating a C#/.NET CGI Executable

I get a similar error to the others, In my browser window it displays:
"CGI Error
The specified CGI application misbehaved by not returning a complete set of HTTP headers. The headers it did return are:


Unhandled Exception: System.ArgumentException: The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))"

I added a try {} catch {} block that encompasses the whole program, but it doesn't capture the error, so the error is somewhere else -- I'm not that familiar with CGI, but it doesn't look like a permissions error to me.

gordy
June 08, 2007

# re: Creating a C#/.NET CGI Executable

I got rid of the E_INVALIDARG exception by changing the CGI app properties to give group Users "Full control" security permissions.

Sean
January 02, 2008

# re: Creating a C#/.NET CGI Executable

Hi Rick.

I have found a use for this, but need to know what extra code would be required in order to actually process the page in the PATH_TRANSLATED environment variable and return the generated HTML. Is this possible?

Rick Strahl
January 02, 2008

# re: Creating a C#/.NET CGI Executable

All Server variables are in the Environment. Environment.GetEnvironmentVariable["PATH_TRANSLATED"] should do the trick.

Of course various Web Servers return the content of this variable differently so you may have to do some additional checking for SERVER_NAME and bracketing.

T. Finegan
April 11, 2008

# re: Creating a C#/.NET CGI Executable

Any ideas as to why a working CSharp .NET managed code exe running as a cgi program within IIS 6 might start throwing an exception *** Unhandled Exception: System.ArgumentException: The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG)) *** just after the Server was bound to an AD domain under a specific OU

Originally the entire folder was authorized using the membership condition --> URL file:///c:/cgibin/* with FullTrust, at the machine level.

Michael Tissington
April 23, 2008

# re: Creating a C#/.NET CGI Executable

I'm have the invalid arg problem too .... I need a solution that will work in a hosted environment ... any ideas please ?

Kizi
May 30, 2008

# re: Creating a C#/.NET CGI Executable


I'm quite new to CGI, but with a very large experience in C# & ASP.NET programming.
I'd like to retrieve a page's content, which is built with IFrames that retrieve a CGI content.
When I capture my WebBrowser document_Complete event, i see an empty innerHTML for those IFrames (src="www.a.com/file1.cgi" attribute...).
My question is how can I retrieve those IFrames' content???

Adam
October 06, 2008

# re: Creating a C#/.NET CGI Executable

I am having the same problem as a few others have mentioned:

Unhandled Exception: System.ArgumentException: The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))

I seen in the Event Viewer that there was a DCOM error. I located the DCOM object in question (Visual Studio Just-In-Time Debugger) and added the "Internet Guest Account" to the Launch Permissions. I am no longer receiving the error in the Event Viewer, but I am still receiving the same error on my web page when attempting to access the cgi script. As well, I have added Full Control permissions to the above mentioned user as well as all other users listed for the directory the file is in as well as the file itself. (This is my local machine, so too many permissions is not a huge deal.)

Please help. This is getting old fast.

SoloFlyer
January 26, 2009

# re: Creating a C#/.NET CGI Executable

to those having problems such as "The specified CGI application misbehaved by not returning a complete set of HTTP headers"

the line after content-type MUST be blank, and have no spaces on it... the example above has a space on that line

@"HTTP/1.1 200 OK
Content-type: text/html
<--- Make sure there is no spaces here!
<html>

andrew
March 01, 2009

# re: Creating a C#/.NET CGI Executable

I've wasted all day trying to get this thing to work and still f***** misbehave error

Shaun
November 02, 2009

# re: Creating a C#/.NET CGI Executable

I tried your example and it works great, i added a WCF web service reference and now get a

The type initializer for 'System.ServiceModel.ClientBase`1' threw an exception.

Help plz.

Chris
March 25, 2010

# re: Creating a C#/.NET CGI Executable

I have the same error as a few people on this page and I was wondering if anyone has found a solution:

Unhandled Exception: System.ArgumentException: The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))

I have no idea what to do!

Many thanks

Chris

Chris
March 25, 2010

# re: Creating a C#/.NET CGI Executable

The reported error may in fact be a known bug. See here:

http://support.microsoft.com/kb/829393/

Any opinions as I am a bit stuck!

Thanks

Chris

hothead420
April 15, 2010

# re: Creating a C#/.NET CGI Executable

First, thanks a bunch for your code, I found the part I was missing. It all worked fine in my test application. But now I'm working on the real application, and this one references a few webservices, and as soon as I try to use any of the webservice's functions I get this :

Unhandled Exception: System.TypeInitializationException: The type initializer for 'System.ServiceModel.ClientBase`1' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.ServiceModel.DiagnosticUtility' threw an exception. ---> System.Configuration.ConfigurationErrorsException: Configuration system failed to initialize ---> System.ArgumentException: Illegal characters in path. at System.Security.Permissions.FileIOPermission.HasIllegalCharacters(String[] str) at System.Security.Permissions.FileIOPermission.AddPathList(FileIOPermissionAccess access, AccessControlActions control, String[] pathListOrig, Boolean checkForDuplicates, Boolean needFullPath, Boolean copyPathList) at System.Security.Permissions.FileIOPermission..ctor(FileIOPermissionAccess access, String path) at System.AppDomainSetup.VerifyDir(String dir, Boolean normalize) at System.Configuration.ClientConfigPaths..ctor(String exePath, Boolean includeUserConfig) at System.Configuration.ClientConfigPaths.GetPaths(String exePath, Boolean includeUserConfig) at System.Configuration.ClientConfigurationHost.get_ConfigPaths() at System.Configuration.ClientConfigurationHost.GetStreamName(String configPath) at System.Configuration.ClientConfigurationSystem..ctor() at System.Configuration.ConfigurationManager.EnsureConfigurationSystem() --- End of inner exception stack trace --- at System.Configuration.ConfigurationManager.EnsureConfigurationSystem() at System.Configuration.ConfigurationManager.GetSection(String sectionName) at System.Configuration.PrivilegedConfigurationManager.GetSection(String sectionName) at System.Diagnostics.DiagnosticsConfiguration.Initialize() at System.Diagnostics.DiagnosticsConfiguration.get_Sources() at System.Diagnostics.TraceSource.Initialize() at System.Diagnostics.TraceSource.get_Listeners() at System.ServiceModel.Diagnostics.DiagnosticTrace.UnsafeRemoveDefaultTraceListener(PiiTraceSource piiTraceSource) at System.ServiceModel.Diagnostics.DiagnosticTrace.CreateTraceSource() at System.ServiceModel.Diagnostics.DiagnosticTrace..ctor(TraceSourceKind sourceType, String traceSourceName, String eventSourceName) at System.ServiceModel.DiagnosticUtility.InitDiagnosticTraceImpl(TraceSourceKind sourceType, String traceSourceName) at System.ServiceModel.DiagnosticUtility.InitializeTracing() at System.ServiceModel.DiagnosticUtility..cctor() --- End of inner exception stack trace --- at System.ServiceModel.DiagnosticUtility.get_Utility() at System.ServiceModel.ClientBase`1..cctor() --- End of inner exception stack trace --- at System.ServiceModel.ClientBase`1.InitializeChannelFactoryRef() at System.ServiceModel.ClientBase`1..ctor() at hcAdmin.UserManager.UserManagerSoapClient..ctor() at hcAdmin.UserManagerCI..ctor() at hcAdmin.Program.UserAlreadyExists(String newUserName, String hostUserName, String hostPassword, String ownerName) at hcAdmin.Program.doAutomatic() at hcAdmin.Program.Main()


I've tried a bunch of things I gathered here and there on forums to try and make it work, but to no vain. I've tried stuff like:
-I've installed the .net framework 3.5 feature on the server
-I've added the I_USR in the security for the folder and even gave it full control for testing purposes.
-I've copied the whole content of the \bin\debug folder to the cgi-win folder, in order to have the APPLICATION.exe.config file.
-I've added the dll's from the \obj\debug\TempPE folder.

Most of that stuff (except the .net framework) didnt make much sense to try (at least to me), but I did anyways. So yeah I'm running out of dumb things to try. Any suggestion?

hothead420
April 26, 2010

# re: Creating a C#/.NET CGI Executable

I managed to get my app to work. Only had to go in the project's properties, in the security tab, and check "Enable ClickOnce security settings". doh

Brown Bear
September 06, 2010

# re: Creating a C#/.NET CGI Executable

For those having problems getting a CGI executable to run properly, remove all of the Console.Write("") references and try the following.

Console.WriteLine("content-type: text/html");
Console.WriteLine("");
Console.WriteLine("HELLO WORLD!");

That should work, and if it doesn't then something else is the problem, however if it does, you should be able to figure out what is different here to what is in the example and then continue building your application accordingly.

Nigel Frost
October 15, 2010

# re: Creating a C#/.NET CGI Executable

to hothead420
Thank you so much!
I've spent a whole day trying to fix this, on an app that was working until I created a new IIS web site for it.
Cheers.

bushman_IL
December 21, 2010

# re: Creating a C#/.NET CGI Executable

Hey Rick, great little article!

Since you obviously know what you're doing, maybe you can throw some guidance my way. I have a .NET Local System Service I created to talk to my application. However, I am now looking to port this application to a web-based version (accessible across all browsers (including mobile). At first I thought about just creating XHTML pages and using a C#.NET backend CGI program to talk to my service on the web server. But it sounds like from what you have indicated CGI isn't on it's way out, it is already out. I realize I could do all this with .NET web services and and ASP.NET page, but then it won't be compatible across all browsers. Do you have any suggestions/guidance for this poor soul? :-)

Thank you!

Rick Strahl
December 21, 2010

# re: Creating a C#/.NET CGI Executable

@brian - ASP.NET is not browser specific. There's just about no reason on the .NET platform to build a CGI executable when you can use an ASP.NET page, or handler, or MVC controller to return a request much more easily in a browser independent manner.

Mikael
July 08, 2012

# re: Creating a C#/.NET CGI Executable

Very interesting, but it doesn't seem to be usable when you are using a hosting company. I can't configure the Script+Exe thingy and I don't think I can do this using web.config. Is it possible to enable CGI/.exe files from the web.config file?

Aleksey
March 28, 2014

# re: Creating a C#/.NET CGI Executable

When I try to call "Environment.GetEnvironmentVariable("REQUEST_METHOD")", I reciev an error "Request for the permission of type 'System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed." What I have to do? Trust level in web.config is "Full".

Paul C
January 03, 2018

# re: Creating a C#/.NET CGI Executable

"Make sure you don't close the Reader() or the request will fail." Can you explain how it fails and why, please? I feel very uncomfortable not closing a resource I have opened!


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024