Using the ASP.NET Runtime to Extend Desktop Applications with HTML Scripts People often think of HTML as the sole domain for Web applications.But HTML's versatile display attributes are also very useful for handling data display of all sorts in desktop applications. The Visual Studio .NET start page is a good example. Coupled with a scripting/template mechanism you can build highly extendable applications that would be very difficult to build using standard Windows controls. In this article, Rick introduces how to host the ASP.NET runtime in desktop applications and utilize this technology in a completely client-side application using the Web Browser control. A few issues back (CoDe Magazine, Nov/Dec 2002) I introduced the topic of dynamic code execution, which is not trivial in .NET. My article generated questions from CoDe readers about how to use this technology in more sophisticated applications. Most of the questions centered around the apparently intriguing topic of 'executing' script pages that use ASP-style syntax. My "Dynamically Executing Code in .NET" article was so long that I didn't have enough room to add an extensive example of how to apply this technology. I will do so this month by rehashing this subject as I show you another more powerful mechanism that's built into the .NET Framework to provide an ASP.NET-style scripting host for client applications. Hosting the ASP.NET Runtime Microsoft made the .NET Framework very flexible, especially in terms of the various sub-systems that make up the core system services. Did you know that you can host the ASP.NET scripting runtime in your own applications? This has several benefits over the ASP-style parsing approach I featured in my last article. Microsoft ships the ASP.NET runtime in the .NET Framework and has made the ASP.NET runtime a system component so you don't have to install anything separately. The runtime is much more powerful than the simple script parser I previously introduced because the runtime supports just about everything that ASP.NET supports for Web pages including all installed and registered languages and ASP.NET-style Web Forms syntax. You can use the runtime to determine if you've previously compiled a page so you don't have to recompile it each time. The ASP.NET runtime handles updates to pages automatically, and as an especially nice bonus you can debug your script pages using the Visual Studio .NET debugger. As always with .NET internals, though, this power comes with a price?overhead and complexity. Visual Studio .NET offers a number of non-obvious ways to accomplish seemingly simple tasks, including passing parameters or leaving the runtime idle for a while. I'll introduce a set of classes that simplify this process down to a few lines of code. I'll also show you the key things that you need to know and implement. You'll find that you can call the ASP.NET runtime from any .NET applications. Follow these three major steps: 1. Set up the runtime environment. You'll tell the runtime which directory to use as its base directory for a Web application (like a virtual directory on a Web Server except here it will be all local files) and you'll set up a new AppDomain that the runtime can execute in.The ASP.NET runtime executes in another AppDomain and all information transmitted between your app and it run over the remoting features of .NET. 2. Create the script page. You will create a single page that contains ASP.NET code. This means you can create pages that contain <% %>, <%= %>, and <script runat="server"> syntax as long as it runs in a single page. You also need to use the appropriate <@Assembly> and <@Namespace> inclusion tags. The script pages can access the current application directory and all assemblies accessible to the current app. 3. Call the script page to execute. You need to tell the runtime which page to execute within the directory tree set up as a 'virtual' in the file system. ASP.NET requires this to find its base directory and associated files. To make the actual call you use the SimpleWorkerRequest class to create a Request object that you will pass to the HttpRuntime's ProcessRequest method. Using the wwAspRuntimeHost Class To simplify the process of hosting the ASP.NET runtime I created a class that wraps steps 1 and 3. Listing 1 shows the code to run a single ASP.NET request from a disk-based script file: You start by instantiating the runtime object and setting the physical disk path where you're hosting the ASP.NET application?you'll put scripts and other script content such as images into this same directory. The Start method launches the ASP.NET runtime in a new AppDomain. My class delegates this process to a Proxy class, AspRuntimeHostProxy, which actually performs all the work. My wwAspRuntimeHost class is simply a wrapper that has a reference to the proxy and manages this remote proxy instance by providing a cleaner class interface and error handling for problems remoting over AppDomain boundaries. Once you've called the Start() method you can make one or more calls to ProcessRequest() with the name of the page to execute in the local directory you set up in cPhysicalPath. You can use any relative path to an ASP.NET page using syntax like "textRepeater.aspx" or "subdir\test.aspx." You can also pass an optional querystring made up of key value pairs that the ASP.NET page can retrieve. My example serves as a simple though limited parameter mechanism. I'll discuss how to pass complex parameters later. In order to generate output from a page request you need to specify an output file with a full path in the cOutputFile property. This file will receive any parsed output that the ASP.NET runtime has parsed?in most cases the HTML result from your script. Although you'll typically generate HTML for display in some HTML rendering format like a Web browser or a Web Browser control (see Figure 1), you can generate output for anything. I often use templates for code and documentation generation that is not HTML.  Figure 1: A simple ASP.NET client script executed locally and then displayed with the Web Browser control. This example passes the repeat count (6 here) as a querystring variable to the ASP.NET script page.Listing 2 shows an example of a simple script page that is a TextRepeater?you type in a string on the form and the script page repeats the text on the form as many times as you specify in the querystring. Figure 1 shows the output form displayed in a Web Browser control. This scripted page is pretty simple but it demonstrates the basic ASP-style scripting behavior that you can perform on a page from embedding expressions (<%= %>), to executing code blocks (<% %>), to defining properties (Repeat) and methods (RepeatText) in the script block, which you can then access in the script or expressions. You can pass simple information to the page using a query string with code like this: this.oHost.ProcessRequest("Test.aspx", "Text=Script This&Repeat=6");
Query strings are encoded key value pairs in the same format as Web page query strings and this example sends two keys?company and repeat. The script page uses the Repeat value: <% string lcRepeat = Request.QueryString["Repeat"]; if (lcRepeat == null) lcRepeat = "1"; this.Repeat = Int32.Parse(lcRepeat); %>
to retrieve the value and convert it into a numeric to pass to the RepeatText() method in the script. Just like ASP.NET pages, you can create script pages that essentially contain properties and custom methods right inside of the script page with: <%= this.RepeatText(Request.QueryString["Text"], this.Repeat) %>
All of the ASP.NET collections are available such as ServerVariables. But not all things that you might use in a Web app might be there, such as SERVER_NAME, REMOTE_CLIENT etc., since these don't apply to local applications. SCRIPT_NAME and APPL_PHYSICAL_PATH, which do return useful values, you might easily use in your application. | " | Remote objects have a limited lifetime of 5 minutes by default. After 5 minutes remote object references are released regardless of whether the object has been called in the meantime.
| " |
If you want to embed images into your HTML, you can do so via relative pathing in the Web directory relative to the output file. Just make sure that you generate the HTML page that you want to render into the base path so that the image pathing works when rendering the form. I used: loHost.cOutputFile = Directory.GetCurrentDirectory() + @"\WebDir\__Preview.htm";
to generate the HTML file into same directory as the ASP.NET virtual path. The sample application shown in Figure 1 lets you display any scripts in the WebDir directory of the sample. When you click on Execute you'll find that it takes 2-4 seconds for the ASP.NET runtime to start up for the first time, but subsequent calls to the same page execute faster. The overhead is both from the runtime loading for the first time as well as the script page being interpreted by the Just in Time Compiler. If you click Execute on the same page a few times you'll have fast performance, but if you click Unload (which calls the Stop() method), the application unloads the runtime and you must reload it on the next hit which again incurs the 2-4 second startup time. Each time you unload the runtime, ASP.NET must recompile each page. You can also edit scripts by clicking on the Edit tab, which contains a textbox with the script code. If you want to change a script, simply make the change and click Save and then press the Execute button again to see the changes displayed in the Web Browser control. Note that when you make changes, the runtime must recompile the page so the first hit is a little slower again. You can also edit the script page in an external editor like VS.NET. Once you've loaded a program or script into an AppDomain you cannot unload it unless you unload the AppDomain. Each change made to a script page adds it to the existing type cache. Internally, ASP.NET does something very similar to the process wwScripting introduced in my last article?it takes a script page and turns it into a class that it compiles and runs on the fly. In order to avoid having too much memory taken up by many scripts and the compilers you can unload the runtime using the Stop method of the wwAspRuntimeHost class. Obviously you can do more complex things in these dynamic pages such as load business objects and retrieve data to display on an ASP.NET style form. We'll look at a more advanced and useful example later. | & | | 
By: Rick Strahl Rick Strahl is president of West Wind Technologies in Maui, Hawaii. The company specializes in Web and distributed application development and tools, with focus on Windows Server Products, .NET, Visual Studio, and Visual FoxPro. Rick is the author of West Wind Web Connection, West Wind Web Store, and West Wind HTML Help Builder. He’s also a C# MVP, a frequent contributor to magazines and books, a frequent speaker at international developer conferences, and the co-publisher of CoDe Magazine. For more information please visit his Web site at www.west-wind.com or contact Rick at rstrahl@west-wind.com.
rstrahl@west-wind.com | Fast Facts | | The ASP.NET runtime in .NET provides the ability to create rich HTML content for your applications dynamically in your desktop applications. It also provides a powerful mechanism for extending the functionality of applications through script code. | |
Using the Web Browser Control in .NET
The .NET Framework doesn't ship with a managed version of Web Browser control that many of us are familiar with. Instead, you have to import the control from the Microsoft Web Browser ActiveX COM control. This process is actually quite painless in .NET: Select the ToolBox on the form you want to drop the control onto, right-click, and select Customize Toolbox. You'll see a list of registered COM components as well as components installed in the Global Assembly Cache. Select Microsoft Web Browser from the COM tab. This adds an icon called Explorer to your Toolbox, which is the Web Browser control. To use the control, just drag and drop it. Unlike the COM control, .NET doesn't support default parameters so calling methods and handling events requires a bit more work than the COM-only counterpart. For example, to call the Navigate method you need to pass all 5 parameters to the method with object references set to null. For example: object loNull = null; loBrowser.Navigate(lcUrl, ref loNull, ref loNull, ref loNull, ref loNull) The control is easy to use but there are problems with some events like BeforeNavigate2 not firing correctly, but for display only purposes the control works very well. Also, keep in mind that the control is based on unmanaged COM code, so if you run the app over a network you may have security issues with the control. |