Rick Strahl's Weblog  

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

Asp.NET Runtime Hosting Update

On this page:


I’m doing a session on hosting the ASP.NET runtime in desktop applications at Portland CodeCamp this weekend, so I had to dust off my old samples and check through the code again. I had written an extensive article on this subject a few years back, that covers this topic in some detail. The article was originally published in Code Magazine in 2002 some time.


Since then I’d been updating some of the classes presented, fixing a few issues that were reported and added some new features that proved useful in my own work.


A few weeks ago I got a message from on user who mentioned that the class doesn’t work in .NET 2.0 and sure enough as I was getting ready to review for the session I found that the class as described in the article doesn’t work with ASP.NET 2.0.


ASP.NET 2.0 breaks my original code

I’m not surprised that the code breaks in 2.0 because I used some custom AppDomain creation that tries to desperately duplicate all the things that ASP.NET requires to efficiently host the ASP.NET runtime in an AppDomain and pass back an Application Host reference.


In the original version of the class I present I implement a custom ASP.NET Runtime Loader method, that replaces the default functionality that ApplicationHost.CreateApplicationHost provides. The problem with CreateApplicationHost is that it is not very configurable. The worst of the problems arises from the fact that if you use a custom hosting class (which you pretty much have to do if you want to do anything useful with the runtime) the assembly that the hosting class resides in must exist in the Web application’s bin directory. This means you have to copy the DLL into the bin directory. Along the same lines if you share any types between the main application and the ASP.NET host environment, you have to make sure that the assemblies exist in both the root application directory and in the ASP.NET host application’s bin directory. Needless to say this is a bit of a hassle as you have to make sure you keep the assemblies in sync.


So when I built my wrapper class, I created a custom method that created an AppDomain and configured it appropriately to be usable as an ASP.NET host AppDomain. Most of the information for this I found by using Reflector to dig into the innards of System.Web.Hosting and picking out the undocument AppDomain configuration settings.


It worked well. The end result was that my CreateApplicationHost method had optional parameters that allowed me to pass in a PrivateBin path that could be assigned to the AppDomain, to have it look in the host application’s execution path for assemblies, which did away with the whole issue of having to copy the assemblies to the Web applications.


public static wwAspRuntimeProxy CreateApplicationHostX(Type hostType,

string virtualDir, string physicalDir,

                        string ApplicationBase, string ConfigurationFile)


      if (!(physicalDir.EndsWith("\\")))

            physicalDir = physicalDir + "\\";


      string aspDir = HttpRuntime.AspInstallDirectory;

      string domainId = "ASPHOST_" + DateTime.Now.ToString().GetHashCode().ToString("x");

      string appName = (virtualDir + physicalDir).GetHashCode().ToString("x");

      AppDomainSetup setup = new AppDomainSetup();


      //    setup.ApplicationBase =  physicalDir;

      //    setup.PrivateBinPath = Directory.GetCurrentDirectory();

      setup.ApplicationName = appName;


      setup.ConfigurationFile = ConfigurationFile;  


      /// Assign the application base where this class' assembly is hosted

      /// Otherwise the ApplicationBase is inherited from the current process

      if (ApplicationBase != null && ApplicationBase != "")

            setup.PrivateBinPath = ApplicationBase;


    AppDomain Domain = AppDomain.CreateDomain(domainId, GetDefaultDomainIdentity(), setup);

      Domain.SetData(".appDomain", "*");

      Domain.SetData(".appPath", physicalDir);

      Domain.SetData(".appVPath", virtualDir);

      Domain.SetData(".domainId", domainId);

      Domain.SetData(".hostingVirtualPath", virtualDir);

      Domain.SetData(".hostingInstallDir", aspDir);


      ObjectHandle oh = Domain.CreateInstance(hostType.Module.Assembly.FullName,


      wwAspRuntimeProxy Host = (wwAspRuntimeProxy) oh.Unwrap();


      // *** Save virtual and physical path so we can tell where app runs later

      Host.VirtualPath = virtualDir;

      Host.PhysicalDirectory = physicalDir;


      // *** Save Domain so we can unload later

      Host.AppDomain = Domain;


      return Host;



The key feature is the PrivateBinPath which adds the application’s base path into the locations that ASP.NET will check for assemblies (ASP.NET adds the bin folder to that list).


Unfortunately, this functionality no longer works with ASP.NET 2.0. It looks like the AppDomain startup and request routing mechanisms still work, but I get a permissions exception that indicates that certain hosting permissions have not been applied. In other words there’s more happening in 2.0 to configure the ASP.NET AppDomain. Looking through System.Hosting.Web I can see that the Hosting setup has become much more sophisticated with a number of new objects (HostingEnvironment, HostingManager), none of which appear to be exposed for direct public consumption.


So, I decided it’s probably not a good idea to continue down this path of creating my own domain. Which brings me back to using the stock ASP.NET CreateApplicationHost() method to create the AppDomain and Host reference.


The new solution

To work around the assembly copy issue I added a property to my wwASPRuntimeHost called ShadowCopyAssemblies. This property is a semi-colon delimited list of assemblies that need to be ‘shadow copied’ to the ASP.NET application’s bin directory. The class does this transparently when the application host is started.


There are two things that need to be copied actually – the assembly that holds the Host type (the class that gets instantiated in the ASP.NET AppDomain and becomes the host as well as any other assemblies that are shared by both the local app and the ASP.NET app (such as when objects are passed to the executing ASP.NET page via the wwASPRuntimeHost.Context object).


Copying of the host assembly is accomplished right inside of the new CreateApplicationHost() method:


public static wwAspRuntimeProxy CreateApplicationHost(Type hostType,

                                                      string virtualDir, string physicalDir)


    if (!(physicalDir.EndsWith("\\")))

        physicalDir = physicalDir + "\\";


    // *** Copy this hosting DLL into the /bin directory of the application

    string FileName = Assembly.GetExecutingAssembly().Location;



        if (!Directory.Exists(physicalDir + "bin\\"))

            Directory.CreateDirectory(physicalDir + "bin\\");


        string JustFname = Path.GetFileName(FileName);

        File.Copy(FileName, physicalDir + "bin\\" + JustFname, true);


    catch { ;}


    wwAspRuntimeProxy Proxy =  ApplicationHost.CreateApplicationHost(




                                                as wwAspRuntimeProxy;


    if (Proxy != null)

        // *** Grab the AppDomain reference and add the ApplicatioÿBase

        // *** Must call into the Proxy to do this




    return Proxy;



This eliminates any need to copy anything in most situations and makes sure the ASP.NET host ‘just runs’ without any configuration. This also means you can dynamically create a directory, copy files into it and start executing ASP.NET pages out of it, which can be very powerful!


For the ShadowCopyAssemblies, I use a method in the top level wwASPRuntimeHost class, which first copies the files specified in the property, then starts the runtime host.


/// <summary>

/// Starts ASP.Net runtime hosting by creating new appdomain and loading runtime into it.

/// </summary>

/// <returns>true or false</returns>

public bool Start()


      if (this.Proxy == null)


            // *** Make sure ASP.Net registry keys exist

            // *** if IIS was never registered, required aspnet_isapi.dll

            // *** cannot be found otherwise



            if (this.VirtualPath.Length == 0 || this.PhysicalDirectory.Length == 0)


                  this.ErrorMessage = "Virtual or Physical Path not set.";

                  this.Error = true;

                  return false;



        // *** Force any assemblies assemblies to be copied





                  this.Proxy = wwAspRuntimeProxy.Start(this.PhysicalDirectory ,



                  // *** Assign these so the proxy knows the paths

            this.Proxy.PhysicalDirectory = this.PhysicalDirectory;

            this.Proxy.VirtualPath = this.VirtualPath;


            catch(Exception ex)


                  this.ErrorMessage = ex.Message;

                  this.Error = true;

                  this.Proxy = null;

                  return false;






      return true;



/// <summary>

/// Copies any assemblies marked for ShadowCopying into the BIN directory

/// of the Web physical director. Copies only

/// if the assemblies in the source dir is newer

/// </summary>

private void MakeShadowCopies(string ShadowCopyAssemblies)


    if (ShadowCopyAssemblies == null ||

        ShadowCopyAssemblies == string.Empty)



    string[] Assemblies = ShadowCopyAssemblies.Split(';', ',');

    foreach (string Assembly in Assemblies)




            string TargetFile = PhysicalDirectory + "bin\\" + Path.GetFileName(Assembly);


            if (File.Exists(TargetFile))


                // *** Compare Timestamps

                DateTime SourceTime = File.GetLastWriteTime(Assembly);

                DateTime TargetTime = File.GetLastWriteTime(TargetFile);

                if (SourceTime == TargetTime)




            File.Copy(Assembly, TargetFile, true);


        catch { ;  } // nothing we can do on failure




Assemblies are copied only if they don’t exist or if the timestamp of the application assembly is not the same.


For application setup to start the runtime and call a page (in this case generically), the whole process now looks like this:


private void LoadRuntime()


      // *** Use a form reference to keep the runtime alive here!

      this.Host = new wwAspRuntimeHost();


      this.Host.PhysicalDirectory = Directory.GetCurrentDirectory() + "\\WebDir\\";

      this.Host.VirtualPath = "/LocalScript";


      // *** ADD

      this.Host.ShadowCopyAssemblies = "AspNetHosting.exe;wwutils.dll";


      this.Host.OutputFile = Directory.GetCurrentDirectory() + "\\WebDir\\__PREVIEW.HTM";

      wwAspRuntimeProxy.IdleTimeoutMinutes = 1;


      if (!this.Host.Start())

            MessageBox.Show("ASP.Net Runtime couldn't start. Error: " + this.Host.ErrorMessage);



private void btnExecute_Click(object sender, System.EventArgs e)


      this.Cursor = Cursors.WaitCursor;


      // *** this code only fires if the runtime was unloaded.

      if (this.Host == null)



      // *** If you use ANY context items on a page and reuse the Host

      // *** you HAVE TO clear the Context items!



      // *** Pass object via the Context collection

      cCustomer Cust = new cCustomer();

      Cust.cCompany = "West Wind Technologies from Frankfurt";




      // *** Optionally pass a POST buffer to ASP.NET page

      this.Host.AddPostBuffer("Company=West+Wind+Technologies GMBH&Name=Rick+Strahl");







      catch(Exception ex)





      if (this.Host.Error)

            MessageBox.Show( this.Host.ErrorMessage );


            this.Navigate("file://" + this.Host.OutputFile);


      this.oPages.SelectedIndex = 0;


      this.Cursor = Cursors.Default;



private void btnUnload_Click(object sender, System.EventArgs e)


      this.Cursor = Cursors.WaitCursor;


      if (this.Host != null)





      this.Cursor = Cursors.Default;




And voila, this code works reliably under .NET 1.1 and 2.0 and presumably in future versions.


It’s too bad that AppDomain configuration or some sort of configuration object is not available for CreateApplicationHost() – it would certainly make life easier, but for the time being this funky shadow copy mechanism actually works well.


A few other notes about ASP.NET 2.0

One other issue I haven’t been able to verify is whether ASP.NET 2.0 installs its registry keys by default. I know Version 1.1 didn’t install the ASP.NET registry keys unless IIS is installed. So the code in the wwASPRuntimeProxy class checks for the keys and if they don’t exist goes ahead and creates them. I’m not sure whether this is still required for 2.0 or not. If it is, the class currently won’t work as it’s not writing the 2.0 specific keys. I would think with the built-in Cassini Web Server and the ability to much more easily host a Web Server in your own applications ASP.NET 2.0 might be writing these keys on install now, but the only way to check this is to install on a machine or VM that doesn’t have IIS installed in the first place.


Another very cool feature of .NET 2.0 is the new HttpListener class, which is basically a Web Server class that you can host in your own applications with a few lines of code. Using a familiar Request and Response object model you can simply fire off a listener and wait for incoming events or fire delegate callbacks in response to incoming requests. I haven’t had much time to look at this, but the quick sample I threw together was really easy to set up and worked well. With my wwASPRuntimeHost class to route requests to the ASP.NET runtime it becomes very simple to set up a self contained Web Server that can handle ASP.NET requests with very little code.


This is very useful for a lot of direct communication applications that can potentially act as client Web servers for callbacks. The only caveat with this new feature is that it requires Windows 2003 Server or XP SP2 because it relies on the new Http.sys kernel mode HTTP driver to feed the Http requests. However, this also means that the HttpListener is using the same high performance Web engine that is used internally by the framework. Reportedly Indigo is using the HttpListener for its Http listener so the same functionality is exposed up to the developer level…


Lots of opportunity in this space to say the very least!


The Voices of Reason


November 28, 2005

# BradA's API Design Hall Of Shame, M3rlin and Synchronicity

taras strypko
December 17, 2005

# re: Asp.NET Runtime Hosting Update

It seems, Rick, i'm forced now to walk your way by cracking [System.Web.Hosting] with Reflector..

Here is my problem: i'm trying to utilise Cassini (indeed, i'd be glad to consider any other alternative to get a trivial built-in web server for my application). I have .NET runtime v2.0.50727 on a IIS-less(!) w2000pro. Now when i instantiate Cassini.Server, i'm getting the following exception:

Exception System.DllNotFoundException was thrown in debugee:
Unable to load DLL 'webengine.dll': The specified module could not be found.

I've traced it up to the ApplicationHost.CreateApplicationHost() call. Also, the above mentioned [webengine.dll] lives patiently in .net folder. It is not a CLR indeed. Unfortunatelly, there is not much rumor in the net regarding this DLL..

Btw, maybe you've got an idea what is 'webengine.dll' for and why couldn't it be found on CreateApplicationHost() call ?

I do only suspect (and you've metioned this) that it's somehow related to IIS-lessness of my system..

Rick Strahl
December 17, 2005

# re: Asp.NET Runtime Hosting Update

WebEnginge.dll probably belongs to Cassini. As to why it's not finding it - any reference to a GAC hosted DLL must be configured in web.config, otherwise ASP.NET will not find it.

FWIW, in ASP.NET 2.0 you can the System.Net.HttpListener to host a Web Server in your application (but you must be an Admin). Hooking up the ASP.NET engine to that is fairly trivial.

January 06, 2006

# re: Asp.NET Runtime Hosting Update

I'm having a similiar issue. I'm trying to use FormsAuthentication.Encrypt from within an assembly called via COM. It's choking with: "Unable to load DLL 'webengine.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)", stack traced to Encrypt calling System.Web.Security.FormsAuthentication.MakeTicketIntoBinaryBlob, which in turn uses System.Web.UnsafeNativeMethods.CookieAuthConstructTicket.

I'm not sure how to best fix this issue. From the error, it's not too clear on whether it's a permissions issue, or just that .NET from ASP COM can't find the dlls.

Any ideas?

January 06, 2006

# re: Asp.NET Runtime Hosting Update

Well, my fix was a simple as adding that framework verisons directory to PATH. The only thing that scares me about that is I have to remember to change that when moving towards newer versions of the framework.

March 17, 2006

# re: Asp.NET Runtime Hosting Update

taras strypko: You might try porting mono's XSP web server as it is all pure .NET code (no depenencies on webengine.dll or http.sys)

Michael Creamer
August 22, 2006

# JavaScript functions from WebResource.axd not available

I've implemented a web server in a VB app (using code similar to that above) that works very well except that JavaScript functions served from a WebResource.axd file (I'm using a Menu control on an .aspx page) do not see to be available.

I can display the retrieved JavaScript content prior to sending it to the browser and it looks fine but the functions cannot be accessed once the page is drawn. If I place the WebResource.axd URL into the browser it retrieves the content just fine.

I was wondering if anyone has noticed anything similar to this? It's really got me stumped.

September 20, 2006

# re: 'webengine.dll' not found

Try disabeling the Visual Studio hosting process (Project -> Debug).

If you have Security Debugging enabled disable it too. (if you do not, visual studio will do so for you)

# DotNetSlackers: Asp.NET Runtime Hosting Update

Stuart Grimshaw
April 25, 2007

# re: Asp.NET Runtime Hosting Update

I'm having this problem as well,

2007-04-25 12:42:01.219#Sdl.Tools.DeploymentChainer.ChainerGui#Unable to load DLL 'webengine.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)
2007-04-25 12:42:01.250#Sdl.Tools.DeploymentChainer.ChainerGui#System.Web
2007-04-25 12:42:01.250#Sdl.Tools.DeploymentChainer.ChainerGui#   at System.Web.UnsafeNativeMethods.CookieAuthConstructTicket(Byte[] pData, Int32 iDataLen, String szName, String szData, String szPath, Byte[] pBytes, Int64[] pDates)
   at System.Web.Security.FormsAuthentication.MakeTicketIntoBinaryBlob(FormsAuthenticationTicket ticket)
   at System.Web.Security.FormsAuthentication.Encrypt(FormsAuthenticationTicket ticket, Boolean hexEncodedTicket)
   at System.Web.Security.FormsAuthentication.Encrypt(FormsAuthenticationTicket ticket)

The .net framework directory is on my path, and webengine.dll is in there.

Any ideas as to what else might cause this?

July 13, 2007

# re: Asp.NET Runtime Hosting Update

Hey there. This is a great article. I am currently struggling with something very similar in .net2.0. The problem that I have is that I need to start the applicationhost through a windows service. I am using the "ApplicationHost.CreateApplicationHost" like you are as above. The problem that I encountered is that when the application runs as a console app it works 100% but when started as a service you get an exception in the eventlog saying that the dll cannot be loaded. The default path that it goes and looks for the assembly is c:windows\system32.

Now the idea you have in your code above with shodow copying might just work. I was wondering if you maybe have a copy of the updated code for .net 2.0 somewhere available for download PLZ. It will be much appreciated.. :)

Felice Pollano Blog
November 23, 2007

# Customizing private path and config file with CreateApplicationHost

Customizing private path and config file with CreateApplicationHost

clone dvd
March 31, 2008

# clone dvd

Gosh provides an alternate way to run command- line Groovy scripts, as well as a simplified way to write Windows Services ( WinNT Services ) using Groovy. Gosh treats scripts the same, whether they are run from the command- line or as a service (including bindings). You can debug a service by running it from the command- line, and you can also run standard command- line Groovy scripts. There is very little difference between a Groovy script that is intended to be run as a Windows Service and any other Groovy script.

dvd player decoders
July 14, 2008

# dvd player decoders

1. Make sure your PC is compatible with the hardware you’ re considering. This is perhaps the most critical part of buying new hardware. If you’ re in the market for a new video card, know if your computer has AGP or PCI- Express when you shop for your card. Nothing is worse than getting home with a nice new video card only to find out you have a different slot. Also keep in mind power requirements. If you didn’ t build your PC, you probably aren’ t aware of your power supply outage unless you’ ve had to...

how to decrypt dvd
July 14, 2008

# how to decrypt dvd

WN: No, because the ones they really like every night, I like, too, like“ On the Road Again.” Or“ Blue Eyes Crying in the Rain” — I didn’ t write it, but it’ s still a great song. “ Always On My Mind” — I didn’ t write that one, either, but I really enjoy singing it. The audience knows that, and they like seeing somebody enjoying what they do.

copy protected dvd
July 15, 2008

# copy protected dvd

They describe themselves as the“ only online dictionary and search engine you need for computer and Internet technology definitions” — and they’ re right!

dvd decrypter burn
July 15, 2008

# dvd decrypter burn

-- WWW Technology Someone\'s Reading Speed Up Sites with php Caching WP- Plugin: AskApache Password Prote Instruct Search Engines to come bac HTTP Packet Capturing to debug Apac Delete extra wordpress files Post- I Redirecting Wordpress Feeds to Feed Fight Blog Spam with Apache Auto- Login to Google Analytics to i Popular View Detailed HTTP Headers. htpasswd file Generator AskApache Search. htaccess files- Ultimate Tutorial Ultimate htaccess File sample Hack WP- Cache for Maximum Speed Mod_ rewrite Tips and...

record from dvd
July 15, 2008

# record from dvd

3) Fix errors where you see them. Similar to the point above, if you see an error… either fix it, or let us know about it. Obviously we’ d prefer that you fix it yourself (using that same Edit link described above), but if you’ re a bit timid about editing web pages in real- time, send us a note and we’ ll take a look as soon as we can.

July 15, 2008

# www.dvdshrinkpro.com

Faithful correspondent Amy Babich commented on the current Chron“ green” issue at some length. In pertinent part, she wrote: Austin’ s green programs have two big problems: They leave out the simplest, cleanest, cheapest solutions, and they leave out poor people. The city of Austin will help you buy a hybrid car, an electric lawn mower, an electric bicycle, or a less- polluting air conditioner. But you can’ t get help with buying an ordinary bicycle, a human- powered mower, or an ordinary electric fan. I...

cd dvd burning
July 15, 2008

# cd dvd burning

Wubi lets you install a complete, fully working Ubuntu system into a standard file held inside your Windows file system. After installing and rebooting it will add a new‘ Ubuntu’ entry to the Windows boot menu, choosing this will boot your new Ubuntu system. No partitions to setup, no Grub or messing with the windows boot loader. If you decide you want to remove Ubuntu you simply run the uninstaller in Windows and away it goes!

Registry Cleaner
July 31, 2008

# Registry Cleaner

Hot on the heels of the Catalyst 8. 3 driver release, Alienware launched their new gaming PC, the Area- 51 ALX CFX. The 4 GPUs come courtesy of paired ATI Radeon HD 3870 X2 video cards, which we reviewed here. Pricing starts at US 5149.

dvd coping
August 03, 2008

# dvd coping

Recent News OS X Curmudgeon? Me? Not By Design Or Intent Test Driving The Firefox 3 Beta 4 Browser Applelinks Tech Web Reader- Friday, March 14, 2008 Applelinks iPhone News Reader- Friday, March 14, 2008 First Updates of the Year: DEVONthink Pro 1. 5. 1 And More Corsair Announces World’ s First High- Performance Mac Memory Upgrade Modules Tivo versus DirecTV HDDVR, Part 2- New on MacOpinion Apple Announces WWDC 2008 Dates Demicron Reveals OS X Version of Its 3D Visualization Tool Wirefusion 5 PDF2Office...

August 03, 2008

# www.easiestdvdripper.com

“ I sat him down and told him what I thought the problems were with security and force protection and turned him loose,” said Sergeant Fenner. “ Within three months, he’ d established new entry control procedures, with Staff Sgt. David Dike, also from 100th SFS. They automated the entry control point system to ensure every visitor and cadet was badged and placed on an entry authority list, so we knew who was there at all times.

submit new site
August 06, 2008

# submit new site

I submit for your approval, the last[ ping] podcast of '07. What a year! Mother Internet has been good to us.

Podcasting Directory
September 24, 2008

# Podcasting Directory

Well said

manoj gupta
October 13, 2008

# re: Asp.NET Runtime Hosting Update

here is manoj gupta
can i run this application without .net framework.

Rick Strahl
October 13, 2008

# re: Asp.NET Runtime Hosting Update


web hosting
December 17, 2008

# web hosting

Recent Comments Sherry on Do You See an Upward Trend in Your January’ s AdSense Earnings? Sherry on Do You See an Upward Trend in Your January’ s AdSense Earnings? Diana on 50 Most Influential Blogs in Malaysia Calvin on Malaysian Can Now Withdraw Money from their PayPal Accounts Gaman on Gobala Krishnan- The New Millionaires– A Review Top Commentators dance aficionado (2) Sherry (2) Norhafidz (1) mansid (1) tunepal (1) himynameis john (1) DA (1) ihateher (1) Gobala Krishnan (1) kk ch (1) Categories Adsense...

February 20, 2009

# re: Asp.NET Runtime Hosting Update

Another thing that might be useful for people struggling with the
Unable to load DLL 'webengine.dll':

I added the C:\Windows\Microsoft.NET\Framework\v2.0.50727 to my PATH variable (start - right click COMPUTER - properties - Advanced System Settings - Advanced - Environment Variables - System Variables - PATH) but it still didn't help because I had to add the path for the 64 bit version C:\Windows\Microsoft.NET\Framework64\v2.0.50727 after that it worked just fine.

Elan Hasson
October 20, 2010

# re: Asp.NET Runtime Hosting Update


Have you had any issues with assigning the wwAspRuntimeProxy's context to the field on the wwAspRuntimeHost?

I keep getting the error "Type 'System.Web.SessionState.HttpSessionState' in Assembly is not marked as serializable."

The web app i'm attempting to host is storing the HttpSessionState in the AppDomain's context.

Any advice?


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