ASPAlliance.com : The #1 Active Server Pages .NET Community The #1 ASP.NET Community
Search   Search

Subscribe   Subscribe

Powered by ORCSWeb Hosting


Site Stats


Powered By ASP.NET
 
Featured Sponsor

Featured Columnist


Featured Book
Professional C# Web Services: Building .NET Web Services with ASP.NET and .NET Remoting
Professional C# Web Services: Building .NET Web Services with ASP.NET and .NET Remoting

Find Prices
Sample Chapter


New! asp.netPRO

We publish our articles in the standard RSS format.

Powerful .NET Email Component

Code Sharing Software
Home Profile Articles Demos Print
Paul Wilson's ASP.NET Corner
Pre-Compiling ASP.NET Web Pages
Pre-Compiling ASP.NET Web Pages

Pre-Compiling ASP.NET Web Pages

Paul Wilson
www.WilsonDotNet.com
www.ASPAlliance.com/PaulWilson

Previous Article            Download DLL/Code            Next Article

Updated Recently -- See Notes and Credits following the Article

Overview

We all know ASP.NET compiles our web pages the first time they are hit, which helps to make our sites faster than was possible using Classic ASP. But a frequent complaint is that this first hit is significantly slower, so many of us have wished for a way to pre-compile our site's web pages. Even those of us that use VS.NET with its compiled code-behind need this, since the aspx content pages are still not compiled until the first hit.

Current Solutions

I know of two current solutions: (1) batch files that hit each page and (2) a single page with lots of IFrames hooked up to hit each page instead. Both of these solutions do work, but they have two significant problems: (1) manual setup of links and (2) starting the batch file or special page. Even if you automate building the links and running script on deployment, you still have problems if your site is restarted or any changes are made.

Better Solution

I have created a GlobalBase class that will automatically hit each page whenever your site is started, either due to a first hit or a restart. You can download the WilsonWebCompile.dll and simply set your Global.asax file to inherit from the Wilson.WebCompile.GlobalBase class -- thats all. Make sure you place the dll into your bin directory, and you will need to also set a reference to it in your project file if you are using VS.NET.
Listing 1: Example Usage
C# CodeBehind -- Global.asax.cs:
public class Global : Wilson.WebCompile.GlobalBase

VB CodeBehind -- Global.asax.vb:
Public Class Global
  Inherits Wilson.WebCompile.GlobalBase

No CodeBehind -- Global.asax:
<%@ Application Inherits="Wilson.WebCompile.GlobalBase" %>

C# Source Code

You don't need the source code if you just use the WilsonWebCompile.dll, but there are some situations that may require modifications to the code. The current GlobalBase class will loop through all of your sub-directories and hit all the aspx files (also ascx, asmx, and ashx) that it finds there. You may not want some sub-directories or files to be included in the loop, or you may have other custom file extensions that you also want compiled.
Listing 2: GlobalBase.cs
using System;
using System.IO;
using System.Net;
using System.Web;
using System.Web.UI;

namespace Wilson.WebCompile
{
  public class GlobalBase : System.Web.HttpApplication
  {
    private static bool needsCompile = true;
    private static string applicationPath = "";
    private static string physicalPath = "";
    private static string applicationURL = "";

    private static System.Threading.Thread thread = null;

    // Override and Indicate Files to Skip with Semi-Colon Delimiter
    protected virtual string SkipFiles {
      get { return @""; }
    }

    // Override and Indicate Folders to Skip with Semi-Colon Delimiter
    protected virtual string SkipFolders {
      get { return @""; }
    }

    public override void Init() {
      if (GlobalBase.needsCompile) {
        GlobalBase.needsCompile = false;

        applicationPath = HttpContext.Current.Request.ApplicationPath;
        if (!applicationPath.EndsWith("/")) { applicationPath += "/";	}

        string server = HttpContext.Current.Request.ServerVariables["SERVER_NAME"];
        bool https = HttpContext.Current.Request.ServerVariables["HTTPS"] != "off";
        applicationURL = (https ? "https://" : "http://") + server + applicationPath;

        physicalPath = HttpContext.Current.Request.PhysicalApplicationPath;
        thread = new System.Threading.Thread(new System.Threading.ThreadStart(CompileApp));
        thread.Start();
      }
    }

    private void CompileApp() {
      CompileFolder(physicalPath);
    }

    private void CompileFolder(string Folder) {
      foreach (string file in Directory.GetFiles(Folder, "*.as?x")) {
        CompileFile(file);
      }

      foreach (string folder in Directory.GetDirectories(Folder)) {
        bool skipFolder = false;
        foreach (string item in this.SkipFolders.Split(';')) {
          if (item != "" && folder.ToUpper().EndsWith(item.ToUpper())) {
            skipFolder = true;
            break;
          }
        }
        if (!skipFolder) {
          CompileFolder(folder);
        }
      }
    }

    private void CompileFile(string File) {
      bool skipFile = false;
      foreach (string item in this.SkipFiles.Split(';')) {
        if (item != "" && File.ToUpper().EndsWith(item.ToUpper())) {
          skipFile = true;
          break;
        }
      }

      if (!skipFile) {
        string path = File.Remove(0, physicalPath.Length);
        if (File.ToLower().EndsWith(".ascx")) {
          string virtualPath = applicationPath + path.Replace(@"\", "/");
          Page controlLoader = new Page();
          try {
            controlLoader.LoadControl(virtualPath);
          }
          finally {
            System.Diagnostics.Debug.WriteLine(virtualPath, "Control");
          }
        }
        else if (!File.ToLower().EndsWith(".asax")) {
          string url = applicationURL + path.Replace(@"\", "/");
          using (HttpWebRequest.Create(url).GetResponse()) {}
          System.Diagnostics.Debug.WriteLine(url, "Page");
        }
      }
    }
  }
}

Conclusion

Using the provided WilsonWebCompile.dll, or modifying the GlobalBase.cs, will finally allow you to always have your ASP.NET automatically compiled with the very first page that is hit, instead of having to hit them all. I've tested this on several sites so far, with different configurations, but I would like to hear from you if your site required any modifications so I can update this article and code with further scenarios for others.

Updates and Credits

I've been amazed how many people have been excited about this possibility, even though its definitely more of a "hack" than a normal "real" solution. I have now updated the code to "hit" user controls, web services, and more, although it still fails with secured sites and should be tested carefully. The async request has been replaced with synchronous on a separate thread to avoid the problems async caused when it used up the available threads. There are also properties you can override in your own Global.asax class to specify any files and/or folders that should be skipped in this process. Finally, see this follow-up article for a similar "hack" that will try to keep your web application always up and running, making it much speedier.

The code in this article has had a few changes since the original release, mostly due to the questions, insights, and contributions from the following:
Christian Weyer sent me the included HttpModule alternative implementation, and was responsible for the ".as?x" filter to handle user controls and more. David Miles converted the original C# to VB, which is included and updated. Joel Gray discovered initial odd behavior with asynchronous web requests, Ambrose Little pointed out hitting pages does not work with secured sites, and Ryan Trudelle-Schwarz provided some feedback and code for user controls. Ambrose also provided some additional user control code while helping me to conclude that another "better" solution was actually not working very well.

Author Bio

Paul Wilson is a software architect in Atlanta, currently with a medical device company. He specializes in Microsoft technologies, including .NET, C#, ASP, SQL, COM+, and VB. His WilsonWebForm Control allows Multiple Forms and Non-PostBack Forms in ASP.NET. He is a Microsoft MVP in ASP.NET and is also recognized as an ASPFriend's ASPAce/ASPElite. He is a moderator on Microsoft's ASP.NET Forums, as well as one of the top posters. He is certified in .NET (MCAD), as well as also holding the MCSD, MCDBA, and MCSE. Please visit his website, www.WilsonDotNet.com, or email him at Paul@WilsonDotNet.com.
 Copyright © 2000-2003 ASPAlliance.com  Page Rendered at 11/22/2009 7:40:00 AM