ASP.NET About Box (Page)

I had a request for an ASP.NET version of my windows forms About Box. This is a good idea that I've considered in the past, so I took the time to convert it today:


This is a companion discussion topic for the original blog entry at: http://www.codinghorror.com/blog/2005/02/aspnet-about-box-page.html

c# version of about.aspx.vb if anyone finds this:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Win32;

namespace Example.AdminSite
{
public class Default : System.Web.UI.Page
{
private String _CallingAssemblyName;
private String _ExecutingAssemblyName;
private System.Reflection.Assembly _EntryAssembly;
private NameValueCollection _EntryAssemblyAttribCollection;

	protected void Page_Load(object sender, EventArgs e)
	{
		_EntryAssembly = Assembly.GetExecutingAssembly();
		_ExecutingAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
		_CallingAssemblyName = Assembly.GetCallingAssembly().GetName().Name;
		// for an ASP.NET app, entry assembly is something of a misnomer;
		// what we really have is ExecutingAssembly
		_EntryAssemblyAttribCollection = AssemblyAttribs(_EntryAssembly);
	}

	private void Page_PreRender(Object sender, System.EventArgs e)
	{
		
	}

	public string AssemblyTitle { get { return EntryAssemblyAttrib("Title"); } }
	public string Copyright { get { return EntryAssemblyAttrib("copyright"); } }
	public string Description { get { return EntryAssemblyAttrib("description"); } }
	public string Company { get { return EntryAssemblyAttrib("company"); } }
	public string Product { get { return EntryAssemblyAttrib("product"); } }
	public string Trademark { get { return EntryAssemblyAttrib("trademark"); } }
	public string Version { get { return EntryAssemblyAttrib("version"); } }
	public string BuildDate { get { return EntryAssemblyAttrib("builddate"); } }
	public string RuntimeVersion { get { return Regex.Match(Environment.Version.ToString(), @"\d+.\d+.\d+").ToString(); } }
	public String EntryAttrib(String key) { return EntryAssemblyAttrib(key); }

	public void RenderDetails()
	{
		RenderAppDetails();
		RenderAssemblyDetails();
	}

	/// summary
	/// exception-safe retrieval of LastWriteTime for this assembly.
	/// /summary
	/// returnsFile.get {LastWriteTime, or DateTime.MaxValue if exception wencountered./returns
	protected internal DateTime AssemblyLastWriteTime(System.Reflection.Assembly a)
	{
		try
		{
			return File.GetLastWriteTime(a.Location);
		}
		catch (Exception exception)
		{
			return DateTime.MaxValue;
		}
	}

	/// summary
	/// returns DateTime this Assembly wlast built. Will attempt to calculate from build number, if possible. 
	/// If not, the actual LastWriteTime on the assembly file will be returned.
	/// /summary
	/// param name="a"Assembly to get { build date for/param
	/// param name="ForceFileDate"Don't attempt to use the build number to calculate the date/param
	/// returnsDateTime this assembly wlast built/returns
	protected DateTime AssemblyBuildDate(System.Reflection.Assembly a, bool ForceFileDate)
	{
		System.Version AssemblyVersion = a.GetName().Version;
		DateTime dt;

		if (ForceFileDate)
			dt = AssemblyLastWriteTime(a);
		else
		{
			dt = new DateTime(2000, 1, 1).AddDays(AssemblyVersion.Build).AddSeconds(AssemblyVersion.Revision * 2);
			if (TimeZone.IsDaylightSavingTime(dt, TimeZone.CurrentTimeZone.GetDaylightChanges(dt.Year)))
				dt = dt.AddHours(1);
			if (dt  DateTime.Now || AssemblyVersion.Build  730 || AssemblyVersion.Revision == 0)
				dt = AssemblyLastWriteTime(a);
		}

		return dt;
	}

	protected DateTime AssemblyBuildDate(System.Reflection.Assembly a)
	{
		return AssemblyBuildDate(a, false);
	}

	/// summary
	/// returns string name / string value pair of all attribs
	/// for specified assembly
	/// /summary
	/// remarks
	/// note that Assembly* values are pulled from AssemblyInfo file in project folder
	///
	/// Trademark    = AssemblyTrademark string
	/// Debuggable   = True
	/// GUID      = 7FDF68D5-8C6F-44C9-B391-117B5AFB5467
	/// CLSCompliant  = True
	/// Product     = AssemblyProduct string
	/// Copyright    = AssemblyCopyright string
	/// Company     = AssemblyCompany string
	/// Description   = AssemblyDescription string
	/// Title      = AssemblyTitle string
	/// /remarks
	private NameValueCollection AssemblyAttribs(System.Reflection.Assembly a)
	{
		String TypeName;
		String Name;
		String Value;
		NameValueCollection nvc = new NameValueCollection();
		Regex r = new Regex("(\\.Assembly|\\.)(?Name[^.]*)Attribute$", RegexOptions.IgnoreCase);

		foreach (Object attrib in a.GetCustomAttributes(false))
		{
			TypeName = attrib.GetType().ToString();
			Name = r.Match(TypeName).Groups["Name"].ToString();
			Value = "";
			switch (TypeName)
			{
				case "System.CLSCompliantAttribute":
					Value = ((CLSCompliantAttribute)attrib).IsCompliant.ToString();
					break;
				case "System.Diagnostics.DebuggableAttribute":
					Value = ((DebuggableAttribute)attrib).IsJITTrackingEnabled.ToString();
					break;
				case "System.Reflection.AssemblyCompanyAttribute":
					Value = ((AssemblyCompanyAttribute)attrib).Company.ToString();
					break;
				case "System.Reflection.AssemblyConfigurationAttribute":
					Value = ((AssemblyConfigurationAttribute)attrib).Configuration.ToString();
					break;
				case "System.Reflection.AssemblyCopyrightAttribute":
					Value = ((AssemblyCopyrightAttribute)attrib).Copyright.ToString();
					break;
				case "System.Reflection.AssemblyDefaultAliasAttribute":
					Value = ((AssemblyDefaultAliasAttribute)attrib).DefaultAlias.ToString();
					break;
				case "System.Reflection.AssemblyDelaySignAttribute":
					Value = ((AssemblyDelaySignAttribute)attrib).DelaySign.ToString();
					break;
				case "System.Reflection.AssemblyDescriptionAttribute":
					Value = ((AssemblyDescriptionAttribute)attrib).Description.ToString();
					break;
				case "System.Reflection.AssemblyInformationalVersionAttribute":
					Value = ((AssemblyInformationalVersionAttribute)attrib).InformationalVersion.ToString();
					break;
				case "System.Reflection.AssemblyKeyFileAttribute":
					Value = ((AssemblyKeyFileAttribute)attrib).KeyFile.ToString();
					break;
				case "System.Reflection.AssemblyProductAttribute":
					Value = ((AssemblyProductAttribute)attrib).Product.ToString();
					break;
				case "System.Reflection.AssemblyTrademarkAttribute":
					Value = ((AssemblyTrademarkAttribute)attrib).Trademark.ToString();
					break;
				case "System.Reflection.AssemblyTitleAttribute":
					Value = ((AssemblyTitleAttribute)attrib).Title.ToString();
					break;
				case "System.Resources.NeutralResourcesLanguageAttribute":
					Value = ((System.Resources.NeutralResourcesLanguageAttribute)attrib).CultureName.ToString();
					break;
				case "System.Resources.SatelliteContractVersionAttribute":
					Value = ((System.Resources.SatelliteContractVersionAttribute)attrib).Version.ToString();
					break;
				case "System.Runtime.InteropServices.ComCompatibleVersionAttribute":
					System.Runtime.InteropServices.ComCompatibleVersionAttribute x;
					x = attrib as System.Runtime.InteropServices.ComCompatibleVersionAttribute;
					Value = string.Format("{0}.{1}.{2}.{3}", x.MajorVersion, x.MinorVersion, x.RevisionNumber, x.BuildNumber);
					break;
				case "System.Runtime.InteropServices.ComVisibleAttribute":
					Value = ((System.Runtime.InteropServices.ComVisibleAttribute)attrib).Value.ToString();
					break;
				case "System.Runtime.InteropServices.GuidAttribute":
					Value = ((System.Runtime.InteropServices.GuidAttribute)attrib).Value.ToString();
					break;
				case "System.Runtime.InteropServices.TypeLibVersionAttribute":
					System.Runtime.InteropServices.TypeLibVersionAttribute tlva = attrib as System.Runtime.InteropServices.TypeLibVersionAttribute;
					Value = tlva.MajorVersion + "." + tlva.MinorVersion;
					break;
				case "System.Security.AllowPartiallyTrustedCallersAttribute":
					Value = "(Present)";
					break;
				default:
					// debug.writeline("** unknown assembly attribute '"  TypeName  "'")
					Value = TypeName; break;
			}

			if (string.IsNullOrEmpty(nvc[Name]))
				nvc.Add(Name, Value);
		}

		// add some extra values that are not in the AssemblyInfo, but nice to have
		// codebase
		try { nvc.Add("CodeBase", a.CodeBase.Replace("file:///", "")); }
		catch (System.NotSupportedException ex) { nvc.Add("CodeBase", "(not supported)"); }

		// build date
		DateTime dt = AssemblyBuildDate(a);
		if (dt == DateTime.MaxValue)
			nvc.Add("BuildDate", "(unknown)");
		else
			nvc.Add("BuildDate", dt.ToString("yyyy-MM-dd hh:mm tt"));

		// location
		try { nvc.Add("Location", a.Location); }
		catch (System.NotSupportedException ex) { nvc.Add("Location", "(not supported)"); }

		// version
		try
		{
			if (a.GetName().Version.Major == 0  a.GetName().Version.Minor == 0)
				nvc.Add("Version", "(unknown)");
			else
				nvc.Add("Version", a.GetName().Version.ToString());
		}
		catch (Exception ex) { nvc.Add("Version", "(unknown)"); }

		nvc.Add("FullName", a.FullName);
		return nvc;
	}

	/// summary
	/// Render a row with the specified key and value strings
	/// /summary
	public void RenderRow(String Key, String Value)
	{
		if (string.IsNullOrEmpty(Value)) return;
		Response.Write("TR");
		Response.Write("TD");
		Response.Write("SPAN class='key'");
		Response.Write(Key);
		Response.Write("/SPANTD");
		Response.Write("SPAN class='value'");
		Response.Write(Value);
		Response.Write("/SPAN/TR");
		Response.Write(Environment.NewLine);
	}

	/// summary
	/// populates the Application Information listview
	/// /summary
	private void RenderAppDetails()
	{
		Response.Write("DIV class='header'Application Details/DIV");
		Response.Write("TABLE width=\"100%\"");
		Response.Write("TRTH width=\"200px\"KeyTH width=\"*\"Value/TR");

		System.AppDomain d = System.AppDomain.CurrentDomain;
		RenderRow("Application Name", d.SetupInformation.ApplicationName);
		RenderRow("Application Base", d.SetupInformation.ApplicationBase);
		RenderRow("Cache Path", d.SetupInformation.CachePath);
		RenderRow("Configuration File", d.SetupInformation.ConfigurationFile);
		RenderRow("Dynamic Base", d.SetupInformation.DynamicBase);
		RenderRow("Friendly Name", d.FriendlyName);
		RenderRow("License File", d.SetupInformation.LicenseFile);
		RenderRow("private Bin Path", d.SetupInformation.PrivateBinPath);
		RenderRow("Shadow Copy Directories", d.SetupInformation.ShadowCopyDirectories);
		RenderRow("Executing Assembly", _ExecutingAssemblyName);
		RenderRow("Calling Assembly", _CallingAssemblyName);

		Response.Write("/TABLE");
		Response.Write("P/P");
	}

	/// summary
	/// RenderRow Assembly Information listview with ALL assemblies
	/// /summary
	private void RenderAssemblyDetails()
	{
		Response.Write("DIV class='header'");
		Response.Write("Loaded Assemblies");
		Response.Write("/DIV");
		Response.Write("TABLE width=\"100%\"");
		Response.Write("TRTH width=\"200px\"AssemblyTH width=\"150px\"Version");
		Response.Write("TH width=\"200px\"BuiltTH width=\"*\"Filename/TR");

		// sort the assemblies first
		SortedList sl = new SortedList();
		foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
		{
			sl.Add(a.GetName().Name, null);
		}

		foreach (DictionaryEntry de in sl)
			RenderAssemblySummary(MatchAssemblyByName(Convert.ToString(de.Key)));

		Response.Write("/table");
		Response.Write("p/p");
		foreach (DictionaryEntry de in sl)
		{
			Assembly a = MatchAssemblyByName(Convert.ToString(de.Key));
			Response.Write("DIV class='header'");
			Response.Write("A name=\"" + a.GetName().Name + "\"" + a.GetName().Name + "/a");
			Response.Write("/DIV");
			Response.Write("BLOCKQUOTE");
			Response.Write("TABLE width=\"100%\"");
			Response.Write("TRTH width=\"200px\"KeyTH width=\"*\"Value/TR");
			RenderAssemblyDetails(a);
			Response.Write("/TABLE");
			Response.Write("/BLOCKQUOTE");
		}
	}

	/// summary
	/// Render Assembly Information listview with summary view for a specific assembly
	/// /summary
	private void RenderAssemblySummary(Assembly a)
	{
		NameValueCollection nvc = AssemblyAttribs(a);
		String strAssemblyName = a.GetName().Name;

		Response.Write("TR");
		Response.Write("TD");
		Response.Write("A href=\"#" + strAssemblyName + "\"");
		Response.Write(strAssemblyName);
		Response.Write("/A");
		if (strAssemblyName == _CallingAssemblyName)
			Response.Write(" (calling)");

		if (strAssemblyName == _ExecutingAssemblyName)
			Response.Write(" (executing)");

		Response.Write("TDSPAN class='version'");
		Response.Write(nvc["version"]);
		Response.Write("/SPANTDSPAN class='builddate'");
		Response.Write(nvc["builddate"]);
		Response.Write("/SPANTDSPAN class='filename'");
		Response.Write(Path.GetFileName(nvc["codebase"]));
		Response.Write("/SPAN/TR");
	}

	/// summary
	/// retrieves a cached value from the entry assembly attribute lookup collection
	/// /summary
	private String EntryAssemblyAttrib(String strName)
	{
		if (string.IsNullOrEmpty(_EntryAssemblyAttribCollection[strName]))
			return "[Assembly: Assembly" + strName + "(\"\")]";
		else
			return _EntryAssemblyAttribCollection[strName].ToString();
	}

	/// summary
	/// Render details for a single assembly
	/// /summary
	private void RenderAssemblyDetails(System.Reflection.Assembly a)
	{
		// this assembly string is only available in framework versions 1.1+
		RenderRow("Image Runtime Version", a.ImageRuntimeVersion);
		RenderRow("Loaded from GAC", a.GlobalAssemblyCache.ToString());

		NameValueCollection nvc = AssemblyAttribs(a);
		foreach (String strKey in nvc)
			RenderRow(strKey, nvc[strKey]);
	}

	/// summary
	/// matches assembly by Assembly.get {Name.Name; returns nothing if no match
	/// /summary
	private Assembly MatchAssemblyByName(String AssemblyName)
	{
		foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
		{
			if (a.GetName().Name == AssemblyName)
				return a;
		}
		return null;
	}


	#region Event wireup

	protected override void OnInit(EventArgs e)
	{
		this.Load += new EventHandler(Page_Load);
		this.PreRender += new EventHandler(Page_PreRender);
		base.OnInit(e);
	}

	#endregion
}

}

Thanks Jeff.
Here is the aspx for C#, which required minimalistic changes:

%@ Page Language=C# AutoEventWireup=true CodeFile=About.aspx.cs Inherits=About %

!DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//EN http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd

html xmlns=http://www.w3.org/1999/xhtml
head id=Head1 runat=server
titleAbout %=Title%/title
meta name=vs_targetSchema content=http://schemas.microsoft.com/intellisense/ie5 /
style type=text/css
body
{ font-family: tahoma; font-size:80%; }
TH
{ text-align: left; font-family: verdana; font-size:80%; }
TD
{ font-size: 80% }
.header
{ background-color: #EEEEEE; padding: 5px; margin:3px; font-size: 120%; font-weight: bold;}
.key
{ color: darkred; font-family: courier;}
.value
{ color: darkblue; font-family: courier; }
.filename
{ font-family: courier; }
.version
{ color: purple;}
.builddate
{ font-style: italic; }
.aboutdiv
{ background-color: #EEEEEE; padding: 10px; }
.aboutdivtitle
{ background-color: white; border: solid 3px #CCCCCC; padding: 5px; margin:10px;}
.title
{ font-size: 140%; font-weight: bold; }
.description
{ font-style: italic; margin-left: 20px; margin: 10px; }
/style
/head
body
form id=form1 runat=server
div class=aboutdiv
table width=95% cellpadding=0 cellspacing=0
tr
td width=500px
div class=aboutdivtitle
span class=title%=Title%/span
/div
div class=description%=Description%/div
/td
td width=*
table
tr
tdVersion %=Version%/td
/tr
tr
tdBuilt on %=BuildDate%/td
/tr
tr
tdRunning on .NET framework version %=RuntimeVersion%/td
/tr
tr
tdCopyright %=DateTime.Now.Year.ToString()%, %=Company%/td
/tr
/table
/td
/tr
/table
%=Product% is %=Copyright%, %=Trademark%
/div
table
!–
Miscellaneous application-related info that’s helpful to display on an About page
(but cannot be automatically derived from assembly attributes)

%
String ExampleConnectionString = Data Source=Your_Server_Name;Initial Catalog=Your_Database_Name;User Id=Your_Username;Password=Your_Password;;
RenderRow(Current URL, Regex.Replace(Request.Url.ToString(), [^/]?$, ));
RenderRow(DB Connection, Regex.Replace(ExampleConnectionString, (password=)([^;]+), $1
*****, RegexOptions.IgnoreCase));
%
/table
input type=button value=Details gt;gt; onclick=this.style.display=‘none’; document.getElementById(‘Details’).style.display=‘block’ /
div style=‘display:none;’ id=‘Details’
%RenderDetails();%
/div
/form
/body
/html