using System; using System.Net; using System.Web; using System.Collections.Specialized; using System.Text; using System.IO; /* * www.ConceptDevelopment.NET * 27 March 2005 * * This code based on sample posted here http://jonijpn.jugem.cc/?eid=21 * * [ORIGINAL instructions] * Simply point your browser to <c>http://yoursite/precompile.axd</c>, * and wait until your CPU usage down to its normal state. * You may add some improvement, make a progress status page to tell when it is done. * I left it for you to have some exercise. * Hint: make use of multithreading. * This precompile technic also demonstrate how easy it is to make an ISAPI extension using ASP.NET. * [/ORIGINAL instructions] * */ namespace ConceptDevelopment.Web { /// <summary> /// HttpHandler that 'precompiles' an ASP.NET website by /// * determining the root directory /// * determining the base Url /// * creating a list of pages recursively from the root directory (and then its subdirectories) /// * looping through each page, requesting it, thus causing the ASPX to be 'compiled' /// * outputting a results page, similar to Trace.axd /// </summary> /// <remarks> /// Firstly, compile this page into an assembly (DLL). /// In the command prompt, navigate to /// C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 (or 1.0.3705 for Framework 1.0) /// /// Then call the C# compiler (CSC) to compile the library into the /bin/ directory /// C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322>csc /target:library /out:c:\inetpub\wwwroot\bin\ConceptDevelopment.Web.Precompile2.dll C:\inetpub\wwwroot\precompile2.axd.cs /// /// To use, add the following to your <c>web.config</c> /// /// <c><configuration> /// <system.web> /// <httpHandlers> /// <add verb="*" /// path="precompile.axd" /// type="ConceptDevelopment.Web.Precompile2, ConceptDevelopment.Web.Precompile2" /> /// <!-- type="NAMESPACE.CLASS, PROJECTDLLNAME" --></c> /// /// Then trigger the Handler via your web-browser /// /// <c>http://yoursite/precompile.axd</c> /// /// NOTE: /// The CSS and Html layout of the results have been 'borrowed' from /// the built-in <c>Trace.axd</c> page -- that's why the page looks familiar :) /// </remarks> public class Precompile2 : IHttpHandler { /// <summary> /// Context.Request (the request that this Handler is running in, /// set in ProcessRequest() /// </summary> private HttpRequest _request = null; /// <summary> /// Collection of page Urls to load/cause-to-compile /// GLOBAL field accessed from within the ReadFilesAndDirectories /// </summary> private StringCollection _dict = new StringCollection(); /// <summary> /// Private constructor /// </summary> private Precompile2() {} #region Obsolete code from original implementation /// <summary> /// [ORIGINAL] implementation started a thread for each page; /// but this doesn't help us get and return the status of each page. /// </summary> /// <remarks> /// This 'private class' is has been replaced by the <see cref="PrecompilePage"/> method /// </remarks> [Obsolete] private class Worker { private string _url = null; public Worker(string url) { _url = url; } public void DoTask() { new System.Threading.Thread(new System.Threading.ThreadStart(this.Run)).Start(); } protected void Run() { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url); StreamReader reader = new StreamReader(request.GetResponse().GetResponseStream()); reader.ReadToEnd(); } } #endregion #region Do Stuff (get the Url, Directory and Compile Pages) /// <summary> /// Parse the virtual directory to determine: /// - TCP/IP Port (port 80 is 'hidden' as it is the default) /// - Domain Name /// - Application Path/Virtual Directory /// </summary> private string AppVirtualDirectory { get { string port = (_request.Url.Port == 80) ? "" : ":" + _request.Url.Port; StringBuilder temp = new StringBuilder("http://"); temp.Append(_request.Url.Host); temp.Append(port); temp.Append(_request.ApplicationPath); if(!_request.ApplicationPath.EndsWith("/")) temp.Append("/"); return temp.ToString(); } } /// <summary> /// Read .ASPX files and subdirectories recursively, /// starting with the application 'root' /// </summary> /// <param name="physicalAppPath">Physical application path</param> private void ReadFilesAndDirectories (string physicalAppPath) { DirectoryInfo directoryInfo = new DirectoryInfo(physicalAppPath); FileInfo[] files = directoryInfo.GetFiles("*.aspx"); for(int i=0; i<files.Length; i++) { _dict.Add(this.AppVirtualDirectory + files[i].FullName.Replace(_request.PhysicalApplicationPath, "").Replace('\\','/')); } DirectoryInfo[] dirInfo = directoryInfo.GetDirectories(); for(int i=0; i<dirInfo.Length; i++) { if(dirInfo[i].Attributes == FileAttributes.Directory) { this.ReadFilesAndDirectories(dirInfo[i].FullName); } } } /// <summary> /// Requests the given /// </summary> /// <param name="url">Url to request/cause-to-compile</param> /// <returns>Http Status Code string (eg "OK", "404", "500" ...)</returns> private string PrecompilePage (string url) { try { System.Net.HttpStatusCode status; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); HttpWebResponse response = request.GetResponse() as HttpWebResponse;//.GetResponseStream(); /* These lines commented out - they'd cause the code to read in * the entire page Html, which we don't really need once it 'compiles' */ //StreamReader reader = new StreamReader(response.GetResponseStream() ); //reader.ReadToEnd(); status = response.StatusCode; response.Close(); // seemed to be keeping connections open without this return response.StatusCode.ToString(); } catch (Exception) { return "500"; //ex.ToString(); } } #endregion #region IHttpHandler interface implementation /// <summary> /// Process the request, compile the whole website /// </summary> /// <remarks>System.Web.IHttpHandler</remarks> /// <param name="context">Http context used to process the request</param> public void ProcessRequest (HttpContext context) { string status = String.Empty; string cssClass = ""; int remainder = 0; string pageUrl = String.Empty; _request = context.Request; string physicalAppPath = context.Request.PhysicalApplicationPath; // Output formatting context.Response.Write (this.Header); context.Response.Write (String.Format (this.Table1, physicalAppPath) ); context.Response.Write (this.Table2); // Read the files (including those in subdirs) into the _dict collection this.ReadFilesAndDirectories (physicalAppPath); // Loop through the files, requesting each for(int i=0; i<_dict.Count; i++) { pageUrl = _dict[i]; // get the Url to compile from the list of Urls Math.DivRem(i,2,out remainder); // determine white/grey row background for output status = this.PrecompilePage(pageUrl); // do the page request/compile if (status == "OK") { // row is white or grey cssClass = (remainder==0)?"":@" class=""alt"""; status = String.Empty; // clear status="OK" so output is easier to read } else { // row is white or grey, with red text cssClass = (remainder==0)?@" class=""red""":@" class=""altred"""; } context.Response.Write( String.Format( this.TableRow, i, DateTime.Now.ToString(), pageUrl , status , "GET", cssClass ) ); } context.Response.Write(this.Footer); } /// <summary> /// IHttpHandler implementation /// </summary> /// <remarks>System.Web.IHttpHandler</remarks> public bool IsReusable { get { return false; } } #endregion #region Html Formatting /// <summary> /// Page header and stylesheet - similar to Trace.axd /// </summary> private string Header = @" <html> <head> <style type=""text/css""> span.tracecontent { background-color:white; color:black;font: 10pt verdana, arial; } span.tracecontent table { font: 10pt verdana, arial; cellspacing:0; cellpadding:0; margin-bottom:25} span.tracecontent tr.subhead { background-color:cccccc;} span.tracecontent th { padding:0,3,0,3 } span.tracecontent th.alt { background-color:black; color:white; padding:3,3,2,3; } span.tracecontent td { padding:0,3,0,3 } span.tracecontent tr.alt { background-color:eeeeee } span.tracecontent tr.red { color:red } span.tracecontent tr.altred { background-color:eeeeee; color:red } span.tracecontent h1 { font: 24pt verdana, arial; margin:0,0,0,0} span.tracecontent h2 { font: 18pt verdana, arial; margin:0,0,0,0} span.tracecontent h3 { font: 12pt verdana, arial; margin:0,0,0,0} span.tracecontent th a { color:darkblue; font: 8pt verdana, arial; } span.tracecontent a { color:darkblue;text-decoration:none } span.tracecontent a:hover { color:darkblue;text-decoration:underline; } span.tracecontent div.outer { width:90%; margin:15,15,15,15} span.tracecontent table.viewmenu td { background-color:006699; color:white; padding:0,5,0,5; } span.tracecontent table.viewmenu td.end { padding:0,0,0,0; } span.tracecontent table.viewmenu a {color:white; font: 8pt verdana, arial; } span.tracecontent table.viewmenu a:hover {color:white; font: 8pt verdana, arial; } span.tracecontent a.tinylink {color:darkblue; font: 8pt verdana, arial;text-decoration:underline;} span.tracecontent a.link {color:darkblue; text-decoration:underline;} span.tracecontent div.buffer {padding-top:7; padding-bottom:17;} span.tracecontent .small { font: 8pt verdana, arial } span.tracecontent table td { padding-right:20 } span.tracecontent table td.nopad { padding-right:5 } </style> </head> <body> <span class=""tracecontent"">"; /// <summary> /// Page subheading - similar to Trace.axd /// </summary> private string Table1 = @" <table cellspacing=""0"" cellpadding=""0"" border=""0"" style=""width:100%;border-collapse:collapse;"" <tr> <td><h1>Application Precompile</h1></td><td align=""Right"" valign=""Bottom"">[ <a href=""Precompile.axd?clear=1"" class=""link"">re-compile</a> ]</td> </tr><tr> <td><h2><h2><p></td><td align=""Right"" valign=""Bottom""><b>Physical Directory:</b> {0}</td> </tr> </table>"; /// <summary> /// Table header - similar to Trace.axd /// </summary> private string Table2 = @" <table cellspacing=""0"" cellpadding=""0"" border=""0"" style=""width:100%;border-collapse:collapse;""> <tr> <th class=""alt"" align=""Left"" colspan=""5""><h3><b>Pages in this Application</b></h3></th><th class=""alt"" align=""Right"">-</th> </tr><tr class=""subhead"" align=""Left""> <th>No.</th><th>Time of Request</th><th>File</th><th>Status Code</th><th>Verb</th><th> </th> </tr>"; /// <summary> /// Table Row - similar to Trace.axd /// String.Format placeholders: /// 0 Row ID /// 1 Date/Time /// 2 File URL /// 3 HTTP Status Code /// 4 HTTP Verb /// 5 Row CssClass /// </summary> private string TableRow = @" <tr{5}> <td>{0}</td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td><td><a href=""{2}"" class=""tinylink"" target=""_blank""><b><nobr>Browse</a></td> </tr>"; /// <summary> /// Page footer - similar to Trace.axd /// </summary> private string Footer = @" </span> </body> </html>"; #endregion } }