I spent a bit of time tweaking the wwASPRuntimeHost class today as I had several questions in regards to dealing with Cookies and passing data into an application that is hosting the ASP.NET runtime. The wwASPRuntimeHost class provides a wrapper around the ASP.NET runtime hosting process, so that you can use a single class instance to fire up and run ASP.NET pages without a Web Server.
The article above is quite a while old. In fact, this one of the first small projects I worked on in .NET and it shows in the samples, which are not very well laid out. However, the class itself is OK (it could also use some refactoring and proper naming, but… <g>).
If you haven’t done this before, with this class hosting the ASP.NET runtime is as easy as pointing the class at an ASP.NET application directory that contains ASPX/web.config/global.asax file and a bin directory and firing away and then calling ASP. Net pages to display. It makes for a great templating and scripting engine or even a simple Add-In architecture. You can also run entire Web sites without a Web Server altogether by using the Web Browser control to forward the relavent request information directly to ASP.NET.
Basic code looks something as simple as this:
private bool LoadAspRuntime()
{
if (this.Host != null)
return true;
// Go straight to West Wind Web Store sample for debugging
Host.cPhysicalDirectory = @"d:\projects\wwWebStore\";
Host.cApplicationBase = Host.cPhysicalDirectory + "bin";
this.txtScriptFile.Text = "Default.aspx";
Host.cOutputFile = Host.cPhysicalDirectory + "__preview.htm";
wwAspRuntimeProxy.nIdleTimeoutMinutes = 1;
if (!this.Host.Start())
{
MessageBox.Show("ASP.Net Runtime couldn't start. Error: " + this.Host.cErrorMsg);
return false;
}
return true;
}
Then, anytime you need to navigate – usually fired from BeforeNavigateComplete2() in the Web Browser control you can use the following code:
/// <summary>
/// Handles navigating to an ASPX page dynamically. Automatically picks up POST
/// data, redirects and a few other things to handle navigation of the application.
/// </summary>
/// <param name="Url"></param>
/// <param name="PostBuffer"></param>
/// <returns></returns>
protected bool NavigateAspxPage(string Url,byte[] PostData)
{
// *** Clear all Context Items
this.Host.Context.Clear();
// *** If Posting set the Request header
if (PostData != null)
this.Host.AddPostBuffer(PostData,"application/x-www-form-urlencoded");
// *** separate Url and QueryString
string Page = "";
string QueryString = "";
this.ParseUrl(Url,out Page,out QueryString);
// *** Add a custom header
this.Host.AddRequestHeader("referer","My custom referer");
this.Host.AddRequestHeader("user-agent","ASP.Net non-Web Client Browser");
this.Host.AddRequestHeader("cache","no-cache");
// *** Process the request
if (!Host.ProcessRequest(Page,QueryString) )
{
MessageBox.Show("Error executing page: " + this.Host.cErrorMsg);
return false;
}
// *** Handle Redirection
if ( this.Host.ResponseStatusCode == 302)
{
Url =(string) Host.ResponseHeaders["location"];
Url = Url.Replace(this.Host.cVirtualPath + "/","");
return this.NavigateAspxPage(Url,null);
}
this.Navigate("file://" + this.Host.cOutputFile);
return true;
}
You basically load the runtime once, then reuse that single instance on all subsequent page hits. Note that this does a few optional things like setting up headers and handling in this case redirections. This single method can now handle all navigations. There’s a bit more code in the ParseUrl() method which is oversimplified in that it will only work for a single directory:
private void ParseUrl(string Url, out string Page,out string QueryString)
{
string[] Parts = Url.Split( new char[1] { '?' });
QueryString = "";
if (Parts.Length > 1)
QueryString = Parts[1];
// *** fix up to URL syntax always! MUST USE \ syntax for navigation!!!
Url = Parts[0].ToLower().Replace("/",@"\");
// *** Strip off the the physical path 'prefix' directory
Page = Url.Replace(this.Host.cPhysicalDirectory.ToLower(),"");
// *** Strip off 'full' application paths (ie. d:\localscript\pagename.aspx)
string VirtPath = this.Host.cVirtualPath.ToLower().Replace("/",@"\");
int VirtPathIndex = Page.ToLower().IndexOf( VirtPath + @"\");
if (VirtPathIndex > -1)
Page = Page.Substring(VirtPathIndex + VirtPath.Length + 1);
Page = Page.Replace(@"file:\\\","");
Page = Page.Replace(@"file:\\","");
this.Text = Page; // Form Caption
}
This routine basically strips a file system path down into a relative path to the Web root directory on disk. The browser sees everything as file urls while ASP.NET needs all Urls in WebRoot relative Web format (/Page.aspx or admin/pages.aspx or /admin/page.aspx). The routine above would need a bit more work to handle multiple paths as it would need to track current location.
This is all part of the original article, but I never got around to posting some of the samples that use the Web Browser control to fully run a Web site. Roughly the above code is really all that is needed to accomplish this.
So what’s new? Better passing of data via Context object
Well, the original codebase didn’t directly expose the Context.Items collection. Rather I had exposed a single ParameterData property which would post a single object into the ContextCollection and would then also return a return object in ResponseData. I decided to throw both of these properties out and instead implement a real Context collection. You can now use:
Host.Context.Clear() // Must clear out any previously set context items
Customer cust = new Customer(); // must be serializable or MarshallByRefObj type
cust.Name = "Rick Strahl";
Host.Context.Add("Customer",cust);
Host.ProcessRequest(…);
cust = Host.Context["Customer"] as Customer;
Inside of the ASP.NET page you can then retrieve this object or whatever else you assign to the collection like this:
<%
Customer cust = this.Context.Items["Customer"] as Customer;
Response.Write(cust.Name);
%>
The one thing to remember is that if you or the ASP.NET page uses the Context object you should always clear it out. The class basically moves the Context collection into the SimpleWorkerRequest derived Context.Items collection by copying the data on the way in and then the other way on the way out. There’s no way to tell when you’re done with the object so unless you clear the Context the contents will stick around.
Cookies
The other issue a number of people have had questions about is with dealing with cookies. If you run an application without a browser or with a browser against a single Preview file you will need to manually manage the cookies.
The updated class that comes with the article now performs automatic cookie management. The Cookies collection automatically picks up any Cookies set on the server and stores it internally. On the next request the cookie is simply picked back and posted back into the ASP.NET runtime. This means that Sessions are also automatically managed now.
If for some reason you don’t want this automatic cookie handling you can simply do:
Host.Cookies.Clear();
or otherwise selectively remove cookies from the collection.
Couple of gotchas to watch out for
There are a couple of things that don’t work with the browser demo I showed above. First I am unable to get anything but InProc Session state to work with any application I point
Try it out
Most of this new stuff is not in the original article – in fact the original article stuck to the pretty basic hosting stuff, but the sample has several additional demos and you can look at the source to get a better idea on how this stuff works.
You can check out the offline browser demo if you use the demo and run the ASP Unplugged demo. Click on the menu, Start ASP. Net Runtime and point at a directory and it will try to run that application.
Couple of limitations exist with this demo:
You must use InProc Session state. I can’t get State Server or SQL Server to work. No idea why…
The demo works only with straight URLs and HTTP responses. The most notable shortcoming is lack of support for Authentication. If you want to run your app completely ‘offline’ you’ll have to turn off Authentication.
The demo only works with links in the main directory. Pages in sub dirs will load, but you will have problems with relative paths and links beyond that. The ParseUrl method would need some extra work to track the location of the current page and manage relative URLs to make this work better.
Other Posts you might also like