In ASP.NET 2.0 runtime hosting there is a change in recommended objects that are used to get an instance of the ASP.NET runtime fired up. Specificially it looks like the preferred way to host the runtime is by using an ApplicationManager object to instantiate the runtime as opposed to using the ApplicationHost object.
ApplicationManager is actually an object that sits ontop of all running ASP.NET AppDomains and can do things like shut them all down or check for idle status. But other than that there's not much that seems to have changed in terms of external functionality at least.
Note that you can also still use the ApplicationHost class as I described in my Hosting the ASP.NET Runtime in Desktop Applications article a few years back.
Here's a quick sample of the most basic Hosting of the ASP.NET runtime you can do using the new .NET 2.0 hosting classes:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Web;
using System.Web.Hosting;
namespace AspNetHosting
{
public partial class Runtime20 : Form
{
AspNetHost20 Host = null;
public Runtime20()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (this.Host == null)
{
// *** Make sure hosting class assembly exists in virtual BIN directory or GAC
// *** This code creates an instance of our Host object
// *** in the ASP.NET AppDomain - One 'Application', one virtual
ApplicationManager Manager = ApplicationManager.GetApplicationManager();
string AppId = "scripts_" + Guid.NewGuid().GetHashCode().ToString("x");
string Virtual = "/scripts";
string Physical = Environment.CurrentDirectory +
"\\RawRuntime\\";
// *** This code creates an instance of our Host object
// *** in the ASP.NET AppDomain - One 'Application', one virtual
ApplicationManager Manager = ApplicationManager.GetApplicationManager();
this.Host = (AspNetHost20)Manager.CreateObject(AppId, typeof(AspNetHost20),
Virual,
Physical,
false);
}
string OutputFile = Environment.CurrentDirectory +
"\\RawRunTime\\__preview.htm";
string[] CommandLine = this.txtScriptFile.Text.Split(new char[1] { '?' });
string QueryString = "";
if (CommandLine.Length > 1)
QueryString = CommandLine[1];
// *** Call into the ASP.NET AppDomain and run the request to file
bool Result = this.Host.RunRequestToFile(CommandLine[0], QueryString, OutputFile);
if (!Result)
MessageBox.Show("Request processing failed...");
else
// *** Display in a Web Browser Contol on the form
this.oBrowser.Navigate("file://" + OutputFile);
}
}
/// <summary>
/// Entry Point Proxy that talks to the other AppDomain
/// Add any mehtods or properties that you need to communicate.
/// Any properties you add must be serializable!
/// </summary>
public class AspNetHost20 : MarshalByRefObject, IRegisteredObject
{
/// <summary>
/// Runs a request to a string result
/// </summary>
/// <param name="Page"></param>
/// <param name="QueryString"></param>
/// <returns></returns>
public string RunRequest(string Page, string QueryString)
{
TextWriter Output = new StringWriter();
SimpleWorkerRequest Request = new SimpleWorkerRequest(Page, QueryString, Output);
try
{
HttpRuntime.ProcessRequest(Request);
}
catch (Exception ex)
{
string Error = ex.Message;
return null;
}
string lcResult = Output.ToString();
Output.Close();
return lcResult;
}
/// <summary>
/// Runs a request to a file result. Use for browser display.
/// </summary>
/// <param name="Page"></param>
/// <param name="QueryString"></param>
/// <param name="OutputFile"></param>
/// <returns></returns>
public bool RunRequestToFile(string Page, string QueryString, string OutputFile)
{
TextWriter Output = File.CreateText(OutputFile);
SimpleWorkerRequest Request = new SimpleWorkerRequest(Page, QueryString, Output);
try
{
HttpRuntime.ProcessRequest(Request);
}
catch (Exception ex)
{
string Error = ex.Message;
return false;
}
Output.Close();
return true;
}
/// <summary>
/// Overrides the default Lease setting to allow the runtime to not
/// expire after 5 minutes.
/// </summary>
/// <returns></returns>
public override Object InitializeLifetimeService()
{
return null; // never expire
}
#region IRegisteredObject Members
public void Stop(bool immediate)
{
HostingEnvironment.UnregisterObject(this);
}
#endregion
}
}
This is the most bare bones Hosting setup you can create which consists of a host class that gets created in the ASP.NET AppDomain and acts as your remote interface to it. You call methods here and they run in the other AppDomain. The two methods here are RunRequest which returns a string result and RunRequestToFile which writes the Http Repsonse to a file.
Both Run method implement the code that fires the HttpRuntime.ProcessRequest() method and pass an HttpWorkerRequest object. In this case I'm using the stock SimpleWorkerRequest which is absolutely bare bones and doesn't do much for communication beyond handing the querystring into the runtime.
If you want to do things like pass POST data around or access the HttpContext.Items collection you have to implement a custom HttpWorkerRequest class that inherits from SimpleWorkerRequest. Cassini is a good example of that and my previous article also shows a bunch of different features you can implement on the Worker request.
While looking into this again recently I took a look at Cassini 20 again as well. Looks like it has cleaned up quite a bit from the 1.x application. An intersting thing I noticed in Cassini is that it implements both the host and the WorkerRequest in the same class – which is a great idea for keeping all the configuration values in one place instead of passing them. Gotta think about that for my wwAspRuntimeHost class…
The new classes HostingEnvironment and ApplicationManager don't seem to provide a lot of extra functionality except a sort of tracking mechanism for all the active AppDomains/Applications. One nice thing I do see on HostingEnvironment is an Impersonate() method which allows passing an Impersonation token or setting the impersonation to the underlying account.
The one thing that I don't see addressed is that you still can't efficiently customize the startup of the AppDomain. While you can set up your own domain for hosting, it's tough to get all the various custom variables right. In fact, when I converted my original code from the article to run under 2.0 I had to throw out the custom AppDomain creation code I had used in 1.x – it didn't work anymore.
The biggest issue I have is that you can't set the private bin path – it sure would be nice to allow you to set up the hosting environment and point it at directories that hold assemblies the HttpRuntime needs. Without this you have to copy the DLLs into the bin directory of the virtual or force it into the GAC.
For example, if you run download and run Cassini – it will fail to load because of this issue unless you copy the DLL or GAC it. It'd be really useful if there was a hook to get access to the AppDomainSetup object.