Hosting ASP.NET Outside IIS
Overview
There is nothing either good or bad, but thinking makes it so.
-William Shakespeare
A few years ago, when ASP was reigning and thriving, a client asked me to create a tool to make it possible for an ASP application to run without its native runtime environment. At first, I admit, it took me a while to make sense of that apparently weird request: "What is my client asking for, running ASP without the ASP engine? Is that even possible?" My brain promptly started a background thread to dig through the innermost recesses of my mind and retrieve any little piece of information that might be useful in outlining a solution. In the meantime, as the thread went to work, just like any software consultant on the face of the earth would have done, I promptly answered, "Sure, I can help you. Let's discuss the problem further." Admittedly, though, I hadn't the faintest idea of how to meet the request.
The client wanted to publish some content on CDs. The content to be published was subject to frequent updates and a number of runtime and security conditions. The client had the same content already published to an ASP-fueled Web site and hoped to deploy it to a variety of offline media, such as a CD or a DVD. "Where's the problem then?" I thought. "Just copy all the files on the storage medium, install it, and leverage the Personal Web server or the IIS Web server installed on the client machine. However, after conducting a survey with customers, my client realized that only a small percentage of potential users was actually running Personal Web server or IIS. Only a few of them declared themselves ready to upgrade the system to accommodate the new product. As a result, no assumption could be made about their installed software. The final user-requirements paper described the application as a piece of software capable of working like ASP but without any external support from a Web server. I had to devise a runtime engine capable of processing ASP files acting as a local Web server. Well, in ASP 3.0 this was much easier said than done.
One of the highest hurdles to overcome was the tight and rather inextricable connection between IIS, the COM+ environment, and the ISAPI extension running ASP—the asp.dll library. In short, the ASP environment is not designed to be hosted outside IIS and doesn't provide a well-known interface for interacting with a generic host application. I decided to work around the issue by constructing an ASP emulator and integrating it with a custom browser. My client was very happy, and I couldn't resist making my work public. (See the "Resources" section at the end of the chapter.)
Amazingly enough, I presented my work at a couple of conferences at the same time Microsoft presented the first beta of ASP.NET. I was very pleased to find out from the Microsoft speakers at those conferences that ASP.NET would provide a much simpler and built-in model for external hosting. In this chapter, we'll explore the techniques and tricks to host ASP.NET outside the natural habitat of IIS.
Hosting the ASP NET Runtime
Although you'll mostly use ASP.NET in conjunction with IIS, keep in mind that IIS is just one possible host for the ASP.NET runtime engine. ASP.NET is designed in a very modular way and, as such, lends itself quite well to being hosted in external applications—IIS being just one of them. However, being capable of hosting ASP.NET in a custom application doesn't mean you have the off-the-shelf solution to build an offline Web browser to ship on a CD. Hosting the ASP.NET runtime engine in a custom application is only the first step for the delivery of dynamic content in an offline manner. Later in this chapter, we'll specifically tackle the problem of delivering your Web site on a CD.
An ASP.NET application doesn't require IIS as the host module. To some extent, ASP.NET doesn't even require a Web server to run. It exposes a well-known interface that any caller can use to connect to the internal HTTP pipeline and ask it to process a well-formed request. Two classes play a key role in the hosting of the ASP.NET engine—HttpRuntime and ApplicationHost.
As we know from Chapter 2, the HttpRuntime class is the entry point in the pipeline of objects that, much like an assembly line, transforms the original HTTP request for an .aspx resource into fresh HTML text. The ApplicationHost class makes it possible for a client application to host the ASP.NET engine. The class is also responsible for creating the AppDomain in the host process in which all the incoming requests for the new application will be handled.
The HttpRuntime class gets involved only when the application host (be it IIS or something else) has received and possibly preprocessed the request. The application host packs all the information about the request in a class derived from the HttpWorkerRequest abstract class or, more often, from its standard implementation class named SimpleWorkerRequest. After an instance of the request class is ready to use, the host hands the processing over to HttpRuntime by calling the ProcessRequest static method. The following code snippet illustrates the core code that fires the processing of an ASP.NET page:
// Prepare the request class SimpleWorkerRequest req; // Indicate the URL, the query string, and output stream req = new SimpleWorkerRequest(aspxPage, null, Console.Out); // Connect to the ASP.NET pipeline and have the request processed HttpRuntime.ProcessRequest(req);
We repeatedly mentioned throughout this book that HttpRuntime is the entry point in the ASP.NET HTTP pipeline. The preceding code shows how a request for an ASP.NET resource is actually funneled into the pipeline to be processed. Figure 24-1 provides a graphical representation of it.
Figure 24-1: The general diagram that funnels an ASP.NET request into the HTTP pipeline. The diagram on the left represents the most general case; the diagram on the right illustrates the specific case of IIS.
The constructor of SimpleWorkerRequest takes the virtual path of the .aspx resource to process, an optional query string, and the text writer to use for the output. Instead of using the standard output console, one could use a stream writer object to save the HTML code to disk. The client makes a request for an ASP.NET resource—that is, for a resource that the ASP.NET runtime knows how to handle. The form and shape in which this request arrives at the ASP.NET host depends on the client and host involved. When browsers and IIS are involved, the request is an HTTP command. You should note that the request can take any form as long as it contains a URL and optionally a query string.
The ApplicationHost Class
The classes that govern the interaction between the host and the ASP.NET HTTP runtime are located under the System.Web.Hosting namespace. The namespace defines a couple of classes—ApplicationHost and SimpleWorkerRequest. The abstract class HttpWorkerRequest, which is the base class for SimpleWorkerRequest, is defined in the System.Web namespace. The ApplicationHost class enables the host process to create application domains for processing ASP.NET requests. The class is sealed—that is, not inheritable—doesn't implement any interface, and doesn't inherit from any base class other than System.Object.
public sealed class ApplicationHost
The class has a simple programming interface that consists of a single static method—the CreateApplicationHost method.
public static object CreateApplicationHost( Type hostType, string virtualDir, string physicalDir );
The virtualDir argument represents the virtual directory for the application domain being created, whereas the physicalDir argument indicates the file system path behind that virtual path. The physicalDir argument specifies the disk folder from which requested .aspx files have to be loaded for that Web application. These pieces of information are associated with the domain and are consumed by the ASP.NET factory objects to create HttpApplication objects (global.asax) and page objects (*.aspx). However, the first argument, hostType, deserves more of our attention.
The hostType argument of CreateApplicationHost is a Type object that evaluates to the type of the application-defined host class. The CreateApplicationHost method just returns an instance of this user-supplied class. The instance is used to create a bridge between the default AppDomain of the host program and the newly created AppDomain, as shown in Figure 24-2.
Figure 24-2: The host type is instantiated by the host application and creates an instance of the type that represents the request.
The Role of the Host Type
The programming interface of the host type is up to you. It normally provides methods through which a caller can issue requests of ASP.NET pages. If the host type has to emulate the behavior of IIS, and therefore act as a local Web server, it will provide methods to be started and stopped and will listen to port 80 (or whatever other port you decide to use). In this case, the host executable will implement a sort of controller object that starts and stops the listener.
The host type object is a sort of proxy between the core code of the host application and the ASP.NET HTTP runtime living in the target AppDomain that CreateApplicationHost had previously created. When a request arrives, it creates a SimpleWorkerRequest object and passes it to the ASP.NET pipeline. The request object is created in the same AppDomain that will actually service the request. SimpleWorkerRequest is a simple implementation of the base HttpWorkerRequest class and provides the ASP.NET runtime with minimal information, such as the requested URL and the query string. It also receives the ASP.NET output into a stream writer object.
The SimpleWorkerRequest Class
You should extend SimpleWorkerRequest and override the appropriate HttpWorkerRequest methods if you think you need more preprocessing functionality, such as parsing headers and posted data. The SimpleWorkerRequest class has two properties, which are listed in Table 24-1.
Property |
Description |
---|---|
MachineConfigPath |
Gets the full physical path to the machine.config file |
MachineInstallDirectory |
Gets the physical path to the directory where the ASP.NET binaries are installed |
The list of SimpleWorkerRequest methods is longer. The details are provided in Table 24-2. Some of the methods (for example, CloseConnection) are simply blank overridable methods exposed to let you easily write custom code in derived classes. Other methods (for example, GetHttpVersion and GetLocalPort) return default values. If such default values aren't appropriate, you should override these methods too.
Method |
Description |
---|---|
CloseConnection |
When overridden in a derived class, terminates the connection with the client. In this class, it does nothing. |
EndOfRequest |
When overridden in a derived class, performs tasks needed when the current request is complete. In this class, it does nothing. |
FlushResponse |
When overridden in a derived class, performs tasks needed when sending all pending response data to the client. In this class, it does nothing. |
GetAppPath |
Returns the virtual path to the currently executing application. |
GetAppPathTranslated |
Returns the UNC-translated path to the currently executing application. |
GetFilePath |
Returns the physical path to the requested URI. |
GetFilePathTranslated |
Returns the physical path to the requested URI, and translates it from a virtual path. |
GetHttpVerbName |
When overridden in a derived class, returns the HTTP verb. In this class, returns "GET". |
GetHttpVersion |
When overridden in a derived class, returns the version of the HTTP protocol used by the request. In this class, returns "HTTP/1.0". |
GetKnownRequestHeader |
Returns the standard HTTP request header that corresponds to the specified index. |
GetLocalAddress |
When overridden in a derived class, returns the IP address returned in the request header. In this class, returns "127.0.0.1". |
GetLocalPort |
When overridden in a derived class, returns the server port number returned in the request header. In this class, returns 80. |
GetPathInfo |
Returns the additional path information that a URL might contain. For example, for the URL page.htm/tail, the return value is "/tail". |
GetPreloadedEntityBody |
Returns the portion of the HTTP request body that has already been read. |
GetProtocol |
Returns the HTTP or HTTPS, depending on the value returned by the IsSecure method. |
GetQueryString |
Returns the query string specified in the URL. |
GetQueryStringRawBytes |
Returns the response query string as an array of bytes. |
GetRawUrl |
Returns the URL path contained in the request header with the query string appended. |
GetRemoteAddress |
When overridden in a derived class, returns the client's IP address. In this class, returns "127.0.0.1". |
GetRemoteName |
When overridden in a derived class, returns the name of the client computer. In this class, returns "127.0.0.1". |
GetRemotePort |
When overridden in a derived class, returns the client's port number. In this class, returns 0. |
GetServerName |
When overridden in a derived class, returns the name of the local server. In this class, returns "127.0.0.1". |
GetServerVariable |
Returns a single server variable from a dictionary of server variables associated with the request. |
GetUnknownRequestHeader |
Returns a nonstandard HTTP request header value. |
GetUnknownRequestHeaders |
Returns all nonstandard HTTP header name/ value pairs. |
GetUriPath |
Returns the virtual path to the requested URI. |
GetUserToken |
Returns the client's impersonation token. |
HasEntityBody |
Returns a value indicating whether the request contains body data. |
HeadersSent |
Returns a value indicating whether HTTP response headers have been sent to the client for the current request. |
IsClientConnected |
Returns a value indicating whether the client connection is still active. |
IsEntireEntityBodyIsPreloaded |
Returns a value indicating whether all request data is available and no further reads from the client are required. |
IsSecure |
Returns a value indicating whether the connection is secure (using SSL). The default is false. |
MapPath |
Returns the physical path corresponding to the specified virtual path. |
ReadEntityBody |
Reads request data from the client (when not preloaded). |
SendCalculatedContentLength |
Adds a Content-Length HTTP header to the response. |
SendKnownResponseHeader |
Adds a standard HTTP header to the response. |
SendResponseFromFile |
Adds the contents of a file to the response. |
SendResponseFromMemory |
Adds the contents of a byte array to the response and specifies the number of bytes to send. |
SendStatus |
Specifies the HTTP status code and status description of the response; for example, SendStatus(200, "Ok"). |
SendUnknownResponseHeader |
Adds a nonstandard HTTP header to the response. |
SetEndOfSendNotification |
Registers for notification when all the response data is sent. The method requires a delegate that will be called back later. |
The HttpWorkerRequest class contains several abstract methods. The SimpleWorkerRequest class represents a quite simple, but effective, implementation of a request class.
A Sample ASP NET Host
So much for the underpinnings of ASP.NET hosting; now let's write some code to demonstrate how to host ASP.NET in a Windows Forms application. The idea is to use ASP.NET to generate HTML files starting from source .aspx files.
Compiling ASPX Pages to HTML
The following listing illustrates the source code of the host type. The only requirement is that the class is remotable—that is, inherits from MarshalByRefObject.
using System; using System.Web; using System.Web.Hosting; using System.IO; namespace ProAspNet.CS.Ch24 { public class WinAspNetHost : MarshalByRefObject { // Processes the ASPX file and creates the corresponding HTML file public void CreateHtmlPage(string aspx, string query, string html) { StreamWriter writer = new StreamWriter(html); SimpleWorkerRequest req = new SimpleWorkerRequest( aspx, // ASPX file name query, // Query string writer); // Output stream (i.e., Console.Out) HttpRuntime.ProcessRequest(req); writer.Close(); } } }
The WinAspNetHost class has only one method, named CreateHtmlPage. As mentioned earlier, the number and the signature of the host class members are completely up to you. What really matters is that you come up with a programming interface that reflects what you are going to do. In this case, the ultimate goal is fairly simple—just take the .aspx file and translate it to HTML. A SimpleWorkerRequest object easily fits the bill and, in addition, the prototype of its constructor is exactly what we need.
public SimpleWorkerRequest( string pageURL, string queryString, TextWriter outputStream );
The first argument is the name of the .aspx file to process. The second argument is an optional query string. Note that if you use a URL with command-line arguments, it will be mistaken for the real name of the file to open. (IIS in particular separates the URL from the query string while preprocessing the request.) The SimpleWorkerRequest class assumes it will receive the URL and query as distinct entities. Furthermore, note that the initial question mark (?) character of the query string must be removed. Finally, the third argument of the constructor is a stream writer object that will be used to bufferize the output text. Everything the .aspx page will send out through Response is accumulated in this text writer object. The text writer can work on top of a variety of streams, including the output console, strings, and files. In this example, we make the writer work on top of an HTML file. As a result, when we close the writer the HTML file is created.
The real host application is a sample Windows Forms application whose user interface is shown in Figure 24-3.
Figure 24-3: The user interface of the sample host application.
Upon loading, the form initializes the application host using the static CreateApplicationHost method.
void Form1_Load(object sender, System.EventArgs e) { InitHost(); } private void InitHost() { m_host = (WinAspNetHost) ApplicationHost.CreateApplicationHost( typeof(WinAspNetHost), "", Directory.GetCurrentDirectory()); }
The virtual directory is unspecified (the empty string), and the physical path is the current directory. Note that the virtual directory parameter has to be something meaningful only to the host application. IIS requires it to match an entry in the metabase; other hosts might force it to be a physical disk folder or a database key. In this case, the host simply ignores that information.
The physical path—the third argument—must exist, otherwise a file-not-found exception will be thrown. The following code snippet shows what happens when the user clicks the Go button. The host application invokes a method on the host class, and the request gets processed by the ASP.NET runtime.
void buttonGo_Click(object sender, System.EventArgs e) { string aspx = FilePath.Text; m_host.CreateHtmlPage(aspx, null, Path.ChangeExtension(aspx, ".htm")); MessageBox.Show("Done"); }
There is no limit to the ASP.NET features you can use. Feel free to use configuration files, ADO.NET adapters, view state, output caching, XML, HTTP modules, and whatever else involves ASP.NET and the .NET Framework. In addition, the application works regardless of the status of IIS. You can keep IIS running or even stop it—the ASP.NET runtime hosted by the sample application won't be affected. (Bear in mind that IIS is just another application host, although a particularly rich and complex one.)
Locating the Host Type
When you build an ordinary IIS-hosted ASP.NET application, you pay attention to deploying all required assemblies in the global assembly cache (GAC) or the Bin directory of the virtual directory. The locations where the ASP.NET runtime searches for assemblies are the same regardless of the characteristics of the host application. This means that even when hosted by a Windows Forms application, ASP.NET expects to load needed assemblies from the Bin subdirectory of the current physical path. As a result, you must ensure that the assembly that contains the host class is available in the same directory as the Windows Forms executable and also in the Bin subdirectory. For the preceding example to work, you must copy the assembly with the host type in a Bin subdirectory under the current directory of the host executable. I recommend that you code the application host class—the WinAspNetHost class in the previous example—in a separate assembly to make it easy to deploy the assembly in multiple paths.
The Cassini Personal Web Server
So, ASP.NET can be hosted in any managed application. More precisely, the ASP.NET runtime can also be hosted by an unmanaged application as long as it can host the common language runtime (CLR). Incidentally, this is exactly what happens with the ASP.NET worker process that IIS uses to serve .aspx requests—aspnet_wp.exe is a Win32 process, yet it hosts the CLR and subsequently ASP.NET.
ASP.NET has a modular design, and its interaction with the host environment is ruled by clear and well-known policies. As a result, any application—managed and unmanaged—that could guarantee such a habitat immediately becomes a good candidate for hosting ASP.NET.
Although this hosting feature is a quantum leap from Active Server Pages, what are the real-world scenarios in which it really gives you more programming power? Sure you can compile .aspx pages to HTML on the fly and without the involvement of a Web server; and you could perhaps set up an effective viewer for documents. But can you really believe that ASP.NET hosting alone lets you build serverless Web-like applications that run on a local machine without IIS?
If you're not convinced yet, think about what happens if the ASP.NET page contains interactive controls and posts back. Suppose that you compiled an .aspx page into an HTML page; and suppose also that the resultant HTML page is being viewed through Internet Explorer. Imagine the user clicks a link or push button. The browser's engine always processes the click event by packing input values into an HTTP POST command and forwarding it through port 80. What happens next?
Unlike IIS, the host application might not have a listener module active on port 80. As a result, a page-not-found error is returned. (This is exactly what happens with the sample Windows Forms host.) To work around this issue, you need to build a custom layer on top of the browser to intercept the postback and transform it into a local call to a specific component of the host application. In other words, with such a simple host, you can realistically exploit ASP.NET only to render static and read-only pages.
Generally, ASP.NET hosting alone is not sufficient to build a real-world serverless environment to consume Web sites offline. The good news is ASP.NET works without IIS and can be hosted in virtually any Win32 application. The bad news is that to obtain a reasonable functionality, you must write an IIS-like host application that behaves like a mini Web server and understands perfectly what the browser—any browser—does.
The Cassini personal Web server is the ideal tool for this kind of task.
Cassini in Person
The Cassini project (see http://www.asp.net/Projects/Cassini/Download) is just the revisited, ASP.NET-based version of the Personal Web Server we know from Windows 95. It is a compact, local Web server you can integrate and deploy with your solutions. Cassini employs the ASP.NET hosting API to create a simple managed Web server, and the System.Net API is used to handle socket connections. Cassini is available as a standalone download but is also integrated with Web Matrix. And the icing on the cake is that Cassini can be downloaded with full source code.
Figure 24-4 shows the typical architecture of an offline Web application based on Cassini. As you can see, the overall scheme is in no way different than that of a typical Internet-based Web application—only much simpler. It goes without saying that Cassini is neither a full replacement for IIS, nor the Microsoft version of an open-source Web server. Cassini is a local Web server designed for handling local calls directed at local folders.
Figure 24-4: The architecture of an offline Web application using Cassini.
Let's first review the various pieces of technology that work together to set up the Cassini Web server and see how to deploy a Web site offline on a CD.
The Architecture of Cassini
The architecture of the Cassini Web server is shown in Figure 24-5. The Web server is a Windows Forms application named CassiniWebServer.exe.
Figure 24-5: The architecture of Cassini.
You start the Web server by specifying the port number to listen to, the virtual directory to monitor, and the physical path behind that. As mentioned earlier, in this context the virtual directory name is just a unique name you use to address .aspx files in the specified physical path. Note that an IIS virtual directory is simply a name with a few related settings stored in the metabase; because you're not using IIS here, a virtual directory is simply a placeholder for a file system path. You start up Cassini through the form shown in Figure 24-6.
Figure 24-6: The startup form of Cassini.
You should note that Cassini can process only one virtual directory at a time and only accepts requests made through localhost. This is exactly what you need to deploy existing Web sites on offline media such as a CD or a DVD.
Cassini is a managed application whose executables take no more than 70 KB of memory. In addition to the program CassiniWebServer.exe, you also need to take into account the application host DLL—cassini.dll. The application host assembly must be stored in the GAC. To register an assembly with the GAC, you can use the following command-line instruction:
gacutil /i Cassini.dll
Table 24-3 lists all the public classes that make up the application host for the Cassini Web server. All classes are compiled in the cassini.dll assembly.
Class Name |
Description |
---|---|
Server |
Provides the public API to start and stop the Web server, and creates the application host. Caches configuration information such as the port number and virtual directory. |
Host |
Remotable object (inherits from MarshalByRefObject), represents the Cassini bridgehead in the child AppDomain that hosts ASP.NET. The class is in charge of opening a socket on the port and accepts HTTP requests when they arrive. |
Connection |
An instance of this class is created to process the request. It is passed the opened socket and asked to create and activate a new Request object to actually carry out the request. The Request object receives a reference to the Connection object and uses it to write response data to the socket. |
Request |
Inherits from SimpleWorkerRequest, and implements the logic necessary to process the HTTP request. Parses the HTTP payload, processes headers, prepares the response buffer, and finally hands the request over to the ASP.NET HTTP pipeline calling the HttpRuntime.ProcessRequest method. While processing the request and generating the response, ASP.NET calls back some methods of the Request class. The Request's implementation of these methods funnels the calls into the parent Connection object to access the socket. |
The Server class is the entry point in the host executable. Its constructor creates the application host, indicating the Host class as the host type.
void CreateHost() { m_host = (Host) ApplicationHost.CreateApplicationHost( typeof(Host), vdirPath, filesPath); m_host.Configure(this, port, vdirPath, filesPath, _aspnetPath); }
Instances of the Server class are created in the Cassini AppDomain, whereas the Host object belongs to the new AppDomain just created for the processing of HTTP requests. The Server object also provides a sort of public API to start, stop, and configure the Web server. The Cassini Web server sees this object as its unique point of contact with the ASP.NET back end.
The Host class represents a remotable object and, as such, inherits from MarshalByRefObject. A remotable object is an object that can be called from within a different AppDomain. The Host class is responsible for opening a socket on the specified port and listens to incoming packets. When a request arrives and is accepted, the class creates a new Connection object to process the request.
Connection conn = new Connection(this, (Socket) acceptedSocket); conn.ProcessOneRequest();
The Connection class receives the socket and starts working on the HTTP packets. The Connection object is mostly an intermediary between the host and the actual worker request. The Connection first creates the request object and then asks it to process the payload.
Request req = new Request(host, this); req.Process();
The Request object derives from SimpleWorkerRequest. It examines the HTTP packet, extracts and preprocesses the message headers, and prepares the response buffer. When done, it simply hands the request out to the ProcessRequest method of the HttpRuntime object. While carrying out the request, ASP.NET calls back several methods on the Request object. In particular, all the data sent out through the Response object results in a call being made to one of the public methods of the host's worker Request object. The actual implementation of Cassini entails that the Request object passes this response data to the methods of the Connection object which, in turn, will route that through the open socket and the port. For this reason, an instance of the Connection object is passed to the constructor of the Request class.
Cassini is the right tool for building serverless ASP.NET applications because it combines two key features: the ability to host the ASP.NET runtime to process .aspx requests and a simple, local-but-effective Web server infrastructure. With this structure, you don't need custom code running on top of the viewer to successfully process all page postbacks. Cassini is the missing link that now makes it possible to deliver entire Web sites on a CD and without the need of IIS.
Put Your Web Site on a CD
When I first examined Cassini, I created the following steps to test its overall effectiveness and make sure it was indeed a breakthrough in the building of offline Web applications. To begin, I stopped IIS and then launched the Cassini executable. (Note that to test Cassini, you must first stop IIS; otherwise, a conflict in accessing the port will occur.) Furthermore, Cassini processes only calls that go through http://localhost; you can't access the local pages by using the machine name (for example, http:/ /contoso), nor can you access Web pages on a Cassini-equipped machine from a remote machine.
After that step, I opened Internet Explorer and typed http://localhost. To my great surprise, I was running my ASP.NET-based intranet without IIS! It goes without saying that if pages contain links to Internet Web sites, they will work as expected as long as an Internet connection is available.
Step by Step Operations
To successfully pack a Web site on a CD, however, a few preliminary checks must be performed. For one thing, make sure that all the links in the pages are either relative or explicitly rooted to localhost. Otherwise, you get an HTTP 403 error (forbidden).
To deploy the Web site offline, xcopy all the files to the storage medium along with the Cassini executables and the .NET Framework redistributable. Configure the setup to copy the Web site tree and to install the cassini.dll to the client machine's global assembly cache. In addition, you should start the Cassini Web server. A well-done setup, though, would also check for IIS and stop it if it's running. A Web site deployed over a CD can hardly work well with IIS unless the setup program is aware of IIS and configures its metabase accordingly. If you're using Cassini for your offline Web application, you're better off disabling IIS altogether. By the way, this is exactly what Web Matrix does when you try to build an ASP.NET page. Web Matrix includes Cassini, but it allows you to work with IIS if you can provide the page with a true IIS virtual directory. If you choose to use Cassini as the Web server, Web Matrix warns that it's going to stop IIS. From this point of view, Web Matrix behaves just like your ASP.NET host should.
The next listing demonstrates some code you can use in the setup to stop IIS, start Cassini, and open the root of the local host:
using System; using System.Diagnostics; public class OfflineWebSite { public static void Main() { OfflineWebSite o = new OfflineWebSite(); return; } public OfflineWebSite() { StartCassini(); Console.WriteLine("Done."); } private void StartCassini() { // stop IIS Console.WriteLine("Stopping IIS..."); Process p = new Process(); p.StartInfo.FileName = "net.exe"; p.StartInfo.Arguments = "stop iisadmin /Y"; p.Start(); p.WaitForExit(); Console.WriteLine("IIS stopped."); // start Cassini (change the default URL) Console.WriteLine("Starting Cassini..."); p.StartInfo.FileName = "cassiniwebserver.exe"; p.StartInfo.Arguments = "c:\inetpub\wwwroot\intranet 80 /"; p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized; p.Start(); Console.WriteLine("Cassini started."); // open the the localhost Console.WriteLine("Opening the local host..."); Process.Start("http://localhost"); } }
Installing ASP.NET Without IIS
In many cases, ASP.NET is installed on a machine in which IIS is fully configured and running. You should note, though, that many aspects of the ASP.NET engine configuration are contingent upon IIS being installed on the system. If IIS is not present at the time of setup, the ASP.NET ISAPI extension is not installed. In addition, all necessary access-control changes on folders, the creation of the ASPNET account, and application mapping changes for side-by-side execution do not occur.
Note |
Because multiple versions of the .NET Framework can be installed on the same computer, each installation contains an associated version of the ASP.NET ISAPI extension. An ASP.NET application uses aspnet_isapi to determine which version of the .NET Framework to use. Furthermore, an ASP.NET application can be configured to use any of the installed versions of the aspnet_isapi component. To specify the version of aspnet_isapi to use for an application, an application mapping is registered within IIS. An application mapping associates a file extension and HTTP verb with the appropriate ISAPI extension. |
To make it easier for developers and administrators to properly configure the ASP.NET environment, an ad hoc tool is provided—aspnet_regiis.exe. The tool is located in the root folder of the .NET Framework installation path. A different aspnet_regiis.exe is included with each version of the .NET Framework.
The tool has various purposes, but here are two. First, it can be used to register a given application with a particular version of ASP.NET. Second, it can be used to register a particular version of ASP.NET on the machine to perform tasks such as registering the aspnet_isapi extension, setting up the worker process account, granting permission on folders, and so forth.
To register an application with a particular version of the .NET Framework, you use the following syntax, in which what follows the –s switch is the path of the application:
aspnet_regiis.exe -s W3SVC/1/ROOT/MyApp
The version of the .NET Framework is the version to which the utility belongs. To configure ASP.NET to work, you use the –i switch.
aspnet_regiis.exe -i
Using the –i switch is required when you install ASP.NET on a machine that lacks IIS. This means that ASP.NET won't work as expected after you install Cassini or any other host application. For ASP.NET to work, you must first run aspnet_regiis.exe with the –i switch to configure ASP.NET and update mappings.
Conclusion
Although it has been fully documented only with ASP.NET 1.1, the System.Web.Hosting namespace has been part of ASP.NET since the first beta. That ASP.NET can be hosted by any managed executable is definitely great news. Does this mean we can now solve an old problem and xcopy a Web site to a CD or a DVD? Well, not exactly.
Cassini is the other side of the coin and a crucial tool for getting ASP.NET applications to run offline and in lower-end, serverless scenarios. We need a tool like Cassini also because offline Web applications need a mini Web server that behaves like a real Web server and that listens to port 80 but accepts only calls that go through the local host. With Cassini—which, by the way, is a free download with source code included—you don't need to install IIS and you can still enjoy the power of a true ASP.NET application deployed locally.
A Web development environment that's completely independent from the Web server and capable of working within a non-Web server application is a kind of dream, especially if you've spent years working with ASP and IIS. Well, wake up because ASP.NET is pure reality!
Resources
- A Client-side Environment for ASP Pages—Part 1 (http://msdn.microsoft.com/msdnmag/issues/0900/cutting/default.aspx)
- A Client-side Environment for ASP Pages—Part 2 (http://msdn.microsoft.com/msdnmag/issues/1000/cutting/default.aspx)
- When IIS and ASP.NET Don't Get Along (http://www.aspnetpro.com /features/ 2003/01/asp200301kd_f/asp200301kd_f.asp)
- Web Matrix (http://www.asp.net/webmatrix/download.aspx)
Final Thoughts
Microsoft started working on what would eventually become ASP.NET soon after shipping the first version of ASP—which happened around the spring of 1997. People embraced ASP as a powerful tool to build Web applications without the need to invest too much time in untangling, and then learning, the intricacies of Web programming. ASP also allowed for the reuse of some existing COM and scripting skills. Although it wasn't quite the perfect tool, it was enough to bring Web programming to the masses. Over the years, one million developers have chosen ASP.
While successful and widely employed in thousands of real-world projects, ASP was only the first step along what was clearly a longer road. The more one used ASP, the more he or she realized that more was needed. ASP had the power of whetting the programmer's appetite, making him or her ask and wish for more. Coming up with a programming platform that was better than ASP proved to be difficult, however. But finally, there was ASP.NET.
This book attempted to cover all programming aspects of ASP.NET and to provide insight about its internal architecture. The ultimate goal of the book is to take less advanced people to a more advanced level of knowledge. Normally, advanced books skip over basic topics. A less-advanced book needs to cover all topics but focuses on the underlying infrastructure more than the high-level interface. This book is for users who want a resource that covers ASP.NET architecture, programming, and advanced stuff. I firmly believe that once you know the underlying API and infrastructure of ASP.NET, you can do whatever you want with ASP.NET, whenever and wherever you want to. I tried to explain the plumbing and the underpinnings of ASP.NET at various levels—including HTTP runtime, standard controls, configuration, security, intrinsics, and custom controls. I also tried to intersperse architectural tips with practical code and design principles. This book has many facets, and I really hope you'll find it useful and informative.
Writing the book has been a seven-month adventure. I haven't spent all this time hiding in a cave with the tapping of the keyboard as the only sign of life to the external world. I shared ideas, solutions, and code snippets with many of you through classes, seminars, conferences, and e-mail. Many of you loved the sneak preview and found even those small pieces of information useful. However, for the most part, this book is based on Microsoft public documentation that you can access via MSDN. Often, information you need can be found there, only a few clicks away—information that is public, clear, and concise. The information might not look as organic and personalized as a book could present it, but contentwise it is equivalent. Searching for information is an art, much like producing content.
My mom used to say, "Ask if you don't know." But I confess that I modified her advice to a more profitable, "Search for, and if you can't find it, just ask."