Hosting the ASP.NET runtime in your own applicationApplies to:
Summary: In this second article you will learn how to create an application that enables running an ASP.NET application from a cd-rom. Download the sample code for this article at the author's website. IntroductionIn the previous part of this article I explained how easy it is to host the ASP.NET runtime in your own application. We created an ASP.NET runtime host and 2 clients using that host: a Windows Forms client (a simple browser in fact) and a command-line tool (console application). Now, we want to do even more: what about hosting an ASP.NET website on read-only media (for example a cd-rom)? It would be great to redistribute our ASP.NET application on a cd-rom since we would have a far more powerful platform for our application (in comparison with a set of static HTML pages). To do this, we can't simply use an application as we wrote in the first part of the article. One of the problems there was linking between pages and performing postbacks to the 'server', which didn't exist in fact. Today we're going to create (to be honest, we'll reuse) a light-weight web server and redistribute this on a cd-rom. Allow me to tell a short story before we kick off. I'm moderator on the ASP.NET forums and one of the questions which is posted very regularly out there in the 'Getting started' forum is the next one: "Why do I need to run a web server to see the results of an .aspx page?'. This is in fact a typical question for a beginner and the reply on such a question is always the same: "since ASPX pages are processed server-side", etc. When you've finished developing the application presented in this article, things will be even more confusing... Can you imagine the reaction of an ASP.NET beginner which has posted the question I mentioned earlier on the forum and has got the answer that a web server is needed? Yes, I know I can. So, to summarize, this article is certainly not intended for beginning (ASP.NET) developers. ![]() Application designTo start, I'm going to have a look at the design of the application. Since .aspx pages require processing by a web server (or at least by the ASP.NET runtime as mentioned in the previous part of the article), it's not possible to request an .aspx file that is stored locally on the machine using a web browser. When using static HTML pages, it is very easy to create a cd-rom that contains a browsable website. So, I'll create an application that can be stored on the cd-rom and will be launched when the cd-rom is inserted. That application won't host the ASP.NET runtime the way I did before in the first part of this article. Why? I've answered that question already in the previous article: there are problems with postbacks and linking of pages. Our application will launch the Cassini web server instead so that we can access our application via the HTTP protocol itself, which will avoid all the problems we mentioned earlier. The entire solution will exist of:
![]() The Cassini web serverOkay, I already said we'll be 'recycling' the code of the Cassini web server. To get started, I'll explain briefly what the Cassini web server is and how it works. The Cassini web server is a lightweight, free and open-source project written in C# by the ASP.NET Team and was developed as a solution for users what want to kick of with ASP.NET but don't have IIS on their machines, which is the case on Windows XP Home clients for example. Users of the ASP.NET Web Matrix will have used the Cassini web server probably since it's integrated in this tool to run and test ASP.NET applications written in ASP.NET Web Matrix. As you can see in the next image, a web server is launched when the F5 key is pressed inside the ASP.NET Web Matrix to run the application: ![]() Although it's called the "ASP.NET Web Matrix Server" over here, it's actually the Cassini web server being launched. ![]() The first you should do to develop the sample discussed in this article, is to download the Cassini source code from the ASP.NET website at http://www.asp.net/Projects/Cassini/Download/Default.aspx?tabindex=0&tabid=1. Download the bits of the "Cassini Sample Web Server". If you've read the previous part of this article you'll recognize the System.Web.Hosting namespace in the short description of the 'tool'. Okay, let's download the file (only 217 KB) and launch the installer, which will copy the source code files to the specified location (by default C:\Cassini). When the installed has finished its work, the readme.txt file will be displayed in Notepad containing the instructions about how to run the web server. The entire solution consists of only 8 code files in C#. I'll start to explain what the functions of those files are:
For more info you can read the included readme.txt file. Once the Cassini source is compiled, the server can be launched using the CassiniWebServer.exe executable as follows using 3 parameters:
![]() Creating the Cassini light project in Visual Studio .NETThe first step in the development process of the "cd-rom ASP.NET application host" is the creation of a new Visual Studio .NET project containing the Cassini source. Remark: It's important to note that the Cassini solution (as well as other ASP.NET Source Projects) comes with a little EULA in the EULA.rtf file. This license agreement allows us to extend the Cassini project as we'll be doing in this article. Enough about the theory, let's move on to the exciting work. Start Visual Studio .NET and create a new Class Library project in C# and give it the name 'CassiniLight': ![]() Why CassiniLight? The answer is very simple: it doesn't require to be hosted in the GAC. In fact we'll just need to copy the source of Cassini to our project without any modifications. Tip: If you want to create a cd-rom launcher application with a maximum customization for your company, you can edit the Messages.cs file to have a custom look-and-feel for the messages displayed by our web server. Now copy the original source files of the Cassini project to the solution (except for the AssemblyInfo.cs since no strong name would be needed anymore). In every file, you should change the namespace to be CassiniLight:
When this job is completed, add a reference to the System.Web.dll assembly in the Solution Explorer: ![]() Finally the solution should look like this: ![]() This should compile without build errors. ![]() The Launcher application itselfOkay, our web server is ready now (special thanks to the ASP.NET team of course). Add a new Windows Application project in C# to the solution and call it 'Launcher': ![]() Set the new project to be the startup project for the solution. Then add a reference to the CassiniLight project using the Solution Explorer again: ![]() Delete the Form1.cs file from the solution and add a new windows form called 'Start.cs'. In the source, add a reference to the CassiniLight namespace:
Next, add an entry point for the application using this piece of code:
What we'll do in the code of the Start.cs form is this:
First of all, let's design the form for the Start.cs file. The form will be used to display a simple message on the screen when the application is launching. Set the FormBorderStyle to none and resize the form to a format of 376 x 64 pixels. The StartPosition property should be changed to CenterScreen as well. Now, add a label to the form with the name lblTitle and dock it to the top border of the form (Dock = Top). Set Navy as the ForeColor and 'Sans Serif, 12 pt, Bold' for the Font property. Change the Text of the label to 'Please wait while the application is launching'. Finally, set the TextAlign property to MiddleCenter. Add another label to the form, called lblAction. ForeColor should be Maroon now, the Font 'Sans Serif, 10 pt, Bold' and the Text 'Initializing...'. Set the TextAlign property to MiddleCenter again and finally dock the label to the bottom of the form using Dock = Bottom. Last but not least, add a Timer control to the form called timer with the default properties (not enabled, 100 ms interval). The form should look like this now: ![]() Step 2: The first piece of code Go to the code of the windows form and add this piece of code to the event handler for the Start_Load event. It will enable the timer when the form is loaded:
We're using the timer over here to start the other tasks mentioned before (look for a free port etc). Although you can do the same using a threading application, I'm using a timer to start the task to keep things simple in this demo. Back in the Design mode for the form, double click the timer control to add an event handler for the Tick event of the timer. Step 3: Look for a free port on the computer In the timer_Tick event handler method, add this first code snippet:
This fragment relies on the FindPort method which we still need to write. The FindPort method looks like this:
In this method the code performs a search operation to find a free port in the range 49000 – 49150. Normally, all those ports are not used, so in 99,999% percent of the cases the first port should be free. If a port is found, the port number is returned. If not, -1 is returned but this should never happen. In this piece of code there's an unknown method as well, i.e. PortIsFree:
To run this piece of code, you'll need to import another namespace on top of the class:
The implementation of the method is very simple: it tries to connect to the localhost on the specified port. If it fails, the port is free, otherwise it's being used by another service. Step 4: Start the CassiniLight server Once we've found a free port, we can start the Cassini web server on that port to host our application. At the bottom of the timer_Tick event handler method, add this code:
To get this to work properly a few other namespace imports are needed:
as well as an app.config file. In the Solution Explorer, add a 'Application configuration file' to the solution and modify it as follows:
This setting will allow the developer of a cd-rom to specify the location on the cd-rom where the application is stored. In this case, we'll test our Launcher application with an ASP.NET application inside the webdir subfolder. Finally, in the timer_Tick event handler, the Cassini web server is launched on the free port and with the correct application folder. The virtual path is set to the default "/" value. Step 5: Launching our browser window In this last step, we're going to create a simple browser window to show the ASP.NET application. Start with adding this piece of code at the bottom of the timer_Tick event handler in the Start.cs file:
The Browser class is another windows form in the Launcher solution which will be created right now. In the Solution Explorer, add a new windows form to the solution and call it 'Browser.cs'. Step 6: Creating the simple browser form First of all, import this namespace in the Browser.cs code:
and add this private attribute to the class:
The next task is to change the constructor of the Browser class by changing the constructor of the class like this:
Now, let's design the form itself. Go back to the design of the Browser.cs file, right-click the toolbox and add the 'Microsoft Web Browser' control as we did in the first part of this article: ![]() To continue, drag and drop the Microsoft Web Browser control from the Toolbox on the form and rename it to 'internetExplorer'. Set the Dock property of the browser control to Fill. Finally, we still need to change the WindowState property of the form itself to Maximized. Now, add this code snippet to the Browser_Load event handler method:
This code navigates to the server which was launched in the Start.cs form earlier. As you can see, two additional values are read from the configuration data (app.config), one for the title of the browser window and one for the homepage which should be opened:
Note: Cassini supports default file names as well. By default these are set to default.aspx, default.htm and default.html. You can edit these in the Request.cs class in the private (string array) variable s_defaultFilenames. Finally, add another event handler for the Closed event of the Browser.cs form and add this code:
This will stop the Cassini web server and exit the application. Step 7: First testing It's compile-time: compile the solution in Visual Studio .NET. This should complete without a single error... and it does over here. I hope you're that lucky as well. The next thing we should do is make an ASP.NET application available in the 'webdir' folder we have specified in the configuration file. In the previous part of the article, I created a simple .aspx page with an asp:Calendar control in it. Let's resume the results:
Go to the Windows Explorer and open the bin\Debug subfolder of the Launcher solution. Create a subfolder with the name 'webdir': ![]() Open notepad and type this piece of code:
Save the file in the webdir folder as default.aspx and go back to Visual Studio .NET. Launch the application with that well-known F5 key... The application will look for a free port, an try to start the Cassini web server. However, it will crash with this error message: ![]() The reason is the CassiniLight isn't registered in the GAC. But we don't want to do so. In the previous part of the article I presented the solution for this problem: create a bin folder and put the missing assembly in there. Let's take a look how to do this in our case... Create a new subfolder in the bin\Debug\webdir subfolder of the Launcher solution directory, called 'bin'. So, the result is: bin\Debug\webdir\bin. Copy the CassiniLight.dll and CassiniLight.pdb files from the bin\Debug folder to the bin\Debug\webdir\bin folder. Okay, go back to Visual Studio .NET now and restart the debugger with F5. Now, the server is launched correctly and this page is displayed in our simple browser: ![]() When you click on a date, data is posted back to the server and the result looks as it should look: ![]() Exciting, isn't it? Step 8: Advanced testing This calendar demo is great but probably you like to see a complete (and more complex) application being hosted in our webdir folder? Okay, let's do it! Go back to the ASP.NET community website website and go to the Starter Kits tab. For example, download the Community Starter Kit. Remark: The starter kits provide full-functional web sites written in ASP.NET with the source code and Visual Studio .NET projects available as well. When you've downloaded the starter kit, install it on your machine. This setup will require IIS since the starter kits are developed to be hosted in IIS. However, we can do better using our tool! The community starter kit needs a SQL Server (or MSDE) database as well. Just install everything by following the instructions of the setup. Finally, the starter kit will be launched in Internet Explorer on http://localhost/communitystarterkit and it will look like this: ![]() Of course, this is still hosted in IIS itself. Important: Since the portal starter kit requires a SQL Server database it's not possible to host this on a cd-rom without installing the database locally first. You could however connect to a SQL Server on-line. If you want to create a cd-rom application with database functionality you'll probably use a read-only database (for example when you want to distribute a catalogue of a bookshop) stored in an Access database on the cd-rom. This will be possible in this solution and won't have any performance drawbacks since the database will only have one connection (it's only being used by one user at a time). Time to try this in our application! Go to the Windows Explorer and locate the folder containing the Community Starter Kit application on your machine. In my case, this is C:\Program Files\ASP.NET Starter Kits\ASP.NET Community Starter Kit (CSVS)\CommunityStarterKit but if you can't find it, you can go to the IIS Management MMC and look for the 'communitystarterkit' virtual folder.Copy the complete content of this folder (including all subfolders) to the webdir folder of our application (overwrite the existing default.aspx demo page).Don't forget to check the existence of the CassiniLight files in the bin subfolder of the webdir folder! If everything is on the right place, go back to Visual Studio .NET and launch the application again. Indeed, the result is exactly what I wanted to see (that is, exactly the same as the result in Internet Explorer for the request to the local IIS machine where the starter kit was hosted as well): ![]() But I'm still not happy with the solution... The web server launched very fast once the free port was found, that's not the problem. The problem is the compilation of the ASP.NET pages on the first-time request. I'll try to fix this a little further. Another thing is I want to use Internet Explorer to view the application as well. ![]() Solving the problemsHow can we solve the 'first-time request compilation problem'? I would like to suggest this solution: once the launcher application has started the Cassini web server on the machine, we can perform a first-time request behind the scenes to get the ASP.NET runtime to 'JIT' compile the application. If this is done, the browser is displayed which will result in a much faster response time. But I can already guess your comment... Although this will increase the speed of the browser it will make the launcher slower. This is definitely true but I shouldn't be a real problem. For example, you can play a little video when the cd-rom is started (by changing the Start.cs form to play a video). For promotional cd-roms this shouldn't be a problem at all! Let's dive into the code... Go to the app.config file again and add this new configuration key:
We'll use this key to determine whether an optimization (by doing a first time request) is needed or not. Now, jump to the start.cs code file and locate this piece of code
Between the second and third line, add this code snippet:
This will read the configuration settings and if the optimization is required the PerformInitialRequest method is called, which is listed here:
This requires this additional namespace import:
When you launch the application again, a first time request is made which take a while but the default.aspx page is returned to the client much faster when the browser is displayed: ![]() ![]() Launch Internet ExplorerA custom browser is fine, it actually is Internet Explorer hosted in our own application. But a lot of people like the real Internet Explorer browser more. In this paragraph we're going to take a look at how we can do this. First of all, we should extend the application configuration file again by adding this setting:
Now, go back to the Start.cs file (code view) and locate this line of code:
Replace it by:
As you can see in the code, the '0-action' will cause the custom browser to be launched, where the '1-action' will invoke the StartIE method which will launch Internet Explorer. In all other cases (that's invalid configuration), the server is stopped and the application is exited using this method:
Take a look at the StartIE method:
Again, another namespace import is needed:
This piece of code may look a little strange but I'll explain. Common questions could be: "Why do we need a process with an Exited event han0064ler?" and "Why can't we just start Internet Explorer and quit the application?". The answer is simple in fact: we need to wait till Internet Explorer has been closed by the user before we can stop the Cassini web server that's running inside our application. Remark: I love Visual Studio .NET 2003... And those images show why: ![]() ![]() Finally, let's take a look at the process_Exited event handler:
In this code, the Cassini server is stopped and the application is exited using the Stop method we wrote earlier. Stiil need another test! Press F5 again and take a look at what happens now... Internet Explorer is launched indeed and here's the result: ![]() Looks magic at the first sight but it isn't... As you can see in the Address field in the browser, the port 49000 was used to start the Cassini web server on. Don't close the browser window but take a look at the status of the debugger in Visual Studio .NET. As you can see it's still running: ![]() Now, close the Internet Explorer window and the debugger will stop. This proves that our event handler for the process_Exited event works properly. Finally, make a Release compile of the complete solution. Note that you'll need top copy the webdir folder to the Release folder to test the release version of the Launcher application. ![]() Burning the cd-romThe application is ready. In this last paragraph I’ll explain how to create a cd-rom that hosts an ASP.NET web application. Step 1: Folder structure I suggest this basic folder structure:
Step 2: Configuration The complete Launcher.exe.config file (in the solution called app.config) should look like this:
Step 3: Autorun.ini Last but not least, it’s nice to have a cd-rom with AutoPlay functionality. To do this, create an autorun.ini file in the root of the cd-rom containing:
As you can see, you can specify an icon for the cd-rom as well. Remark: Since this is a C# application the users of the cd-rom will need to have the .NET Framework installed on their pc (redistributable or SDK). ![]() Wrap upPossible implementations
![]() About the author![]() Although being a student Informatics, Bart De Smet is already actively developing applications with the .NET Framework since the first beta. His interests are rather broad: going from C# and Visual Basic .NET development and the internet (ASP.NET, Web Services, .NET Remoting) over systems administration (Windows 2000, Windows XP and Windows Server 2003, Active Directory, Exchange Server 2003) to .NET Enterprise Servers (SQL Server, BizTalk). Bart is also responsible for the maintenance of the network of the "College" in Zottegem (Windows Server 2003) and is actively involved in the Belgian .NET community. In 2001 he built the first site using .NET Passport in Belgium. Recently Bart became a Top 25 Poster and moderator of the official ASP.NET Forums. Bart is also a regular speaker about ASP.NET and Windows Server 2003 on AAL and he also takes care of several workshops.You can contact him at info@bartdesmet.net or on his website http://www.bartdesmet.net Since October 2003, Bart was also appointed as ASP.NET MVP (Most Valuable Professional) by Microsoft. ![]() |