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

Some updates to the wwASPRuntimeHost Class for hosting the ASP.NET runtime


:P
On this page:

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.

 


The Voices of Reason


 

Muhammad Mazhar Karimi
August 09, 2005

# Good for understanding ASP.net Core Request &amp; Response Process

In short Good Article.

Joe Rickicki
October 18, 2005

# re: Some updates to the wwASPRuntimeHost Class for hosting the ASP.NET runtime

My Request.Browser property is null even after setting the request. I'm curious why the header info is not sufficient for the runtime to generate this information. Any ideas on this?


Rick Strahl
October 18, 2005

# re: Some updates to the wwASPRuntimeHost Class for hosting the ASP.NET runtime

What are you setting exactly? You should set te User-Agent headers with Headers.Add(), yeah?

Joe Rickicki
October 18, 2005

# re: Some updates to the wwASPRuntimeHost Class for hosting the ASP.NET runtime

Rick,

Yes, I'm using the this.Host.AddRequestHeader method to add my header info. (I used Fiddler to grab my user-agent header from a request.) After processing the worker request, HttpRequest.UserAgent is set properly, but HttpRequest.Browser is not. I'm wondering if this could be because the user-agent header is not formatted properly. Rick, does your HttpRequest.Browser property contain an instantiated HttpBrowserCapabilites object? I've yet to get it to work, but from your previous response, it sounds as if you have?

- Joe

Rick Strahl
October 18, 2005

# re: Some updates to the wwASPRuntimeHost Class for hosting the ASP.NET runtime

No you have to set up all the objects yourself and BrowserCapabilities is not one that I set up in my code... You can certainly do that on your own though after you read the user agent...

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