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>&lt;configuration>
	/// 	&lt;system.web>
	/// 		&lt;httpHandlers>
	/// 			&lt;add verb="*" 
	///					path="precompile.axd" 
	///					type="ConceptDevelopment.Web.Precompile2, ConceptDevelopment.Web.Precompile2" />
	///				&lt;!-- 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>&nbsp</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
	}
}