Belay That Delay

Precompile your ASP.NET Web sites to avoid first-run slowness.

Ask the Pro

LANGUAGES: C#

TECHNOLOGIES: File I/O | SQL

 

Belay That Delay

Precompile your ASP.NET Web sites to avoid first-run slowness.

 

By Jeff Prosise

 

Q. Is there a tool available for precompiling an ASP.NET Web site so the first request for each page suffers no compilation delay?

 

A. Assuming you enjoy access to pages through the local file system (that is, assuming you can run the tool on the Web server where the pages reside or access the pages through a network share), building a precompilation utility is easy using the rich facilities found in the .NET Framework Class Library.

 

Figure 1 contains the source code for such a utility; it's a console application written in C#. To compile it, open a Visual Studio .NET command prompt window, go to the directory where Touch.cs is stored, and type csc touch.cs. The output is an executable named Touch.exe. To precompile the pages in a specified directory and its subdirectories, execute a command of the form touch url path, in which url is the URL of the virtual root directory and path is the physical path to that directory in the file system. For example, to precompile all the .aspx files in wwwroot and its subdirectories, type touch http://localhost c:\inetpub\wwwroot. This assumes, of course, that wwwroot is located on the C drive.

 

using System;

using System.IO;

using System.Net;

 

class Touch

{

    static void Main (string[] args)

    {

        if (args.Length < 2) {

            Console.WriteLine ("Syntax: TOUCH url path");

            return;

        }

 

         if (!Directory.Exists (args[1])) {

            Console.WriteLine ("Error: Invalid path");

            return;

        }

 

        string url = args[0];

        if (!url.ToLower ().StartsWith ("http"))

            url = "http://" + url;

        string path = args[1];

 

        TouchFiles (url, path);

    }

 

    static void TouchFiles (string url, string path)

    {

        string[] files =

            Directory.GetFiles (path, "*.aspx");

 

        foreach (string file in files) {

            int index = file.LastIndexOf ("\\");

            string page =

                url + "/" + file.Substring (index + 1);

            WebRequest request = WebRequest.Create (page);

 

            try {

                WebResponse response =

                    request.GetResponse ();

                StreamReader reader = new StreamReader

                     (response.GetResponseStream ());

                string content = reader.ReadToEnd ();

                reader.Close ();

                Console.WriteLine ("OK: " + page);

             }

            catch (WebException) {

                Console.WriteLine ("Error: " + page);

            }

        }

 

        string[] dirs = Directory.GetDirectories (path);

 

        foreach (string dir in dirs) {

            int index = dir.LastIndexOf ("\\");

            string nextdir = dir.Substring (index + 1);

            string nexturl = url + "/" + nextdir;

            string nextpath = path + "\\" + nextdir;

            TouchFiles (nexturl, nextpath);

        }

    }

}

Figure 1. Touch.cs precompiles ASP.NET applications by submitting an HTTP request for each .aspx file in a virtual directory and its subdirectories.

 

Touch.exe lists all the pages it fetches (see Figure 2). If "OK" appears in front of the filename, the page was retrieved successfully and it compiled without error. If "Error" appears in front of the filename, the page was not retrieved successfully. This could mean the page contains an error that prevents it from compiling, but that's for you to decide after inspecting the page.

 


Figure 2. Touch.exe lists the pages it retrieves and tells you whether the page executed successfully. The word "Error" preceding a filename could indicate that the page contains a syntax error preventing it from compiling.

 

How does Touch.exe work? It uses the .NET Framework Class Library's System.IO.Directory class to enumerate the files and directories in the specified directory and its subdirectories. Then, it uses System.Net.WebRequest to submit an HTTP request for each .aspx file it finds. If a page fetched this way hasn't been compiled into a Dynamic Link Library (DLL) already, ASP.NET compiles it, averting the compilation delay that normally would occur when a user first accesses the page.

 

Note that Touch.exe does not work with pages that disallow anonymous access because it lacks support for Windows authentication protocols. Nor can it fetch pages protected by forms authentication because it has no means for getting past the login page. One solution to the forms authentication problem is to expose pages protected by forms logins temporarily so they can be accessed without logging in.

 

Q. Are there any tricks I can use to post a page containing a runat="server" form to a page other than itself? Adding an action attribute to the

tag is futile because ASP.NET overrides it with an action attribute that points back to the same page.

 

A. ASP.NET forbids a runat="server" form from posting back to another page, but you might be able to accomplish what you're after by taking a slightly different approach. It turns out that if you remove runat="server" from the tag, ASP.NET won't alter the tag's action attribute. The bad news is a form lacking a runat="server" attribute can't host Web controls. But the good news is it can host HTML controls, which means if you can do without DataGrids, validation controls, and other controls not represented in the HTML controls namespace, you can post back to other pages just fine.

 

To demonstrate, the page in Figure 3 contains a login form that hosts three HTML controls. (Note the runat="server" attributes adorning the control tags but the absence of runat="server" in the tag.) Clicking on the Log In button submits the form to Welcome.aspx (see Figure 4), which reads the username the user typed from the HTTP request and outputs it to the page. Do the runat="server" attributes on the tags perform any useful functions? You bet. They make the controls visible to server-side scripts, which you can demonstrate easily by adding a Page_Load method and using it to initialize one or more of the controls.

 

  

    

      

        

          

          

        

        

  

          

          

        

        

          

          

        

      

User Name:

            runat="server" />

Password:

            runat="server" />

             runat="server" />

    

  

Figure 3. Login.aspx posts back to Welcome.aspx rather than to itself. The secret? Remove runat="server" from the form tag and use HTML controls rather than Web controls.

 

  

    

  

 

Figure 4. Upon postback, Welcome.aspx reads the username the user typed into Login.aspx and uses it to display a customized greeting.

 

In real life, login forms often post back to pages that are accessed over HTTPS. Substitute an HTTPS URL for Welcome.aspx in the action attribute, and this form will do just that.

 

Q. I'm using the DataGrid's built-in paging support to allow users to page through a rather lengthy list of records. One concern I have is even though I set the page size to 50, I must query for all the records and provide the entire recordset - which consists of more than 4,000 records - each time the DataGrid is paged. I understand custom paging is more efficient because it allows me to provide the DataGrid with only the 50 records for the current page, but I can't figure out a way to query SQL Server 2000 for only one page worth of records.

 

A. I consulted with fellow asp.netPRO columnist Dino Esposito and SQL Server guru Peter DeBetta, and I learned a technique they use to custom-page DataGrid controls. You can use a query containing multiple SELECT statements and a NOT IN clause to query SQL Server for a specific range of records. The sample page in Figure 5 demonstrates how. It displays records from the Products table of SQL Server's Northwind database, which contains a total of 77 records, using a page size of 10.

 

<%@ Import Namespace="System.Data" %>

<%@ Import Namespace="System.Data.SqlClient" %>

 

  

    

      

        AllowPaging="true" AllowCustomPaging="true"

        PageSize="10" OnPageIndexChanged="OnPage" />

    

  

 

Figure 5. SmartPaging.aspx uses the DataGrid control's custom-paging feature to display records from SQL Server's Northwind database. It queries for only one page worth of records at a time.

 

Although 77 is a rather small record count, the technique applies to tables of any size. Simply substitute the values of your choice for {0}, {1}, {2}, and {3} in the statement that formulates the query, per the instructions provided in the GetPage method. And, remember that "page number" is 1-based, not 0-based like page indexes.

 

A final thought to keep in mind when paging a DataGrid is you can improve performance by querying all the records you intend to display simultaneously, caching the resulting DataSet and coding your PageIndexChanged handler to fetch the DataSet from memory. The ASP.NET application cache, which you can access through a page's Cache property, provides an ideal mechanism for caching DataSets.

 

The sample code in this article is available for download.

 

Jeff Prosise is author of several books, including Programming Microsoft .NET (Microsoft Press). He also is a co-founder of Wintellect (http://www.wintellect.com), a software consulting and education firm that specializes in .NET. Have a question for this column? Submit queries to [email protected].

 

Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish