A Website Health Check Page

Over the last two years I have supported a multi-function web site with many application settings, three databases, and several external web services. The need to support this site on many different servers led to the development of a detailed diagnostic health check page for the application. Some of the servers are controlled by the customer which makes debugging problems more difficult since you have to coordinate changes with remotely located support persons in different time zones.

The health check we have developed is written in ASP.Net web forms but could be easily converted to an MVC format. The main purpose here is not focus on the presentation layer but to identify useful information to report and present some of the code used to gather information.

The page supports three options:

  1. Simple Health Check – A fast check that does a quick database connection check and returns a code of 200 if successful else a 500. This is performed if there is no valid query string values.
  2. Get Version – Returns a simple one line version of the application version. This is called by using a query string containing a “GetVersion” key with any value.
  3. Detailed Health Check – Does a detailed database check, web services check, and displays some non-sensitive application settings. Called by a query string containing “details” key with any value.

The Database Section

Database section

The header of the page is set to the site name constant, “Mighty Site” in this example. The current URL follows the site name. This is helpful if you have one or more instances open for different locations.

In this example we check three databases. The first check “Database Connections” is the same check called in the simple health check. The function CheckDbConnection is called for each database and attempts to open a new connection. Before trying, the connection timeout is set to 5 seconds. This keeps you from having to wait the standard 30 seconds if the connection string is bad or the database is down.

Next a random stored procedure is executed for each database. This tells you the database login you are using has execute permissions on stored procedures.

Our second database (DB2) needs full text search installed, which is not the default case when you install SQL Server. The GetServerProperty function in the Helper class is called to retrieve the server property “IsFullTextInstalled”.

The next two lines the the date and time of the SQL Server instance and the IIS server. In the example above, both are on the same localhost machine and are the same. But we did have one occasion when the database was on a separate machine and just a few seconds difference caused one pop up routine to go into an infinite loop.

Web Services

Web Service Section - Passing

The Mighty Site relies on a Single Sign On service to log users in, and a Data Service to get and update information about the users. The ServiceHealthCheck function uses the Web API Client to make calls to the health check on these services and looks for a 200 OK return code. If a service cannot be reached or returns a non-200 code, the row background will be pink and a DNS lookup is attempted on the URL.

Web Service Section - Failing

Application Settings

The application has many settings in the appSettings.config file and it is useful to see some non-sensitive ones on the health check page. You could also display database resident settings here.

Application Settings Section

Version Information

The website maintains version information in the App_GlobalResources\About.resx file.

Version Info Section

Time Zone Information

Unfortunately the Mighty Site was written years ago by some folks who didn’t believe in using UTC date times. The system is based on the time zone where the server is located which causes a lot of headaches since different customers are in different time zones.

Time Zone Section

Conclusion

The ASPX file contains only a single line, the usual first line containing the Page, CodeFile, and other directives. All of the page content is generated in the code behind with the help of the functions in the HTML Tables region. The source code is available on GitHub.

VMware Standalone Converter Write Error

I have run the VMware Standalone Converter now from several different machines. Each time I get an error that the file

C:\ProgramData\VMware\VMware vCenter Converter Standalone\ssl\rui.crt cannot be opened.

The file is always there but cannot be accessed. The file properties always show Administrators have full access, and my current user is an administrator. Never the less, just set your logged in user to have full access to the folder and all will work fine.

Jungle Disk Service Hangs Up

I’ve been using Jungle Disk for nearly three years now for reliable, easy, and inexpensive cloud storage. The software uses Amazon S3 storage or their own Rackspace storage. Since I currently run five machines at home and one at work the network drive that Jungle Disk mounts is great for storing large files. It can also be configured to do automatic backups. For things that change everyday, I also run Windows Live Mesh.

Recently on my new Vista machine the Jungle Disk service would just hang in the starting state. I tried reinstalling several times with the same result. I resorted to the Jungle Disk help desk.

The first response was disappointing: make Jungle Disk an exception in the firewall and make sure ports 80 and 443 are open. Since none of the other machines running McAfee had this set, I didn’t think it would help. But of course I tried it to no avail.

I specifically asked how to remove all program information before the installation. Every time I tried reinstalling the software I was never asked for my Amazon S3 information. I had assumed it was a registry entry, but in fact for Vista it is stored in an XML file in C:\ProgramData\JungleDisk. Removing the files in that location fixed the problem and I was able to re-enter the Amazon information and run the program without the service hanging.

Only four or five hours of work for what I guess was a gamma ray striking some part of my disk drive since there had been no configuration changes or installs to prompt this behavior.

WinForm Design Mode Rendering Error

We recently upgraded our main application to the latest version of the Infragistics controls. Everything converted, compiled, and ran fine. However, when you tried to open some forms in the Visual Studio designer, you got the message “The path is not of a legal form”. Looking at the project for these forms, they had invalid references to custom controls that used Infragistics controls. I deleted the references and added the newly compiled versions to eliminate the designer rendering error.

Since we do not remove the previous version of Infragistics the older controls are found at run time.

Visual Studio 2005: Key not valid for use in specified state

We have recently migrated all of our workstations to a different Windows domain at our office. Some folks were getting the following error when trying to open a DataSet in Visual Studio 2005:

Key not valid for use in specified state

The problem can be caused by the change of a username from the old domain to the new domain. Visual Studio is not able to properly decrypt some its settings.

The fix is to rename/delete the following file:

C:\Documents and Settings\YOUR USERNAME\Application Data\Microsoft\VisualStudio\8.0\ServerExplorer\DefaultView.SEView

You will now have to add back all your databases to the Server Explorer toolbox (but you only have to add them at a later time as you need them).

Thanks to Robert Stam for this tip.

.NET Unable to Sign Assemblies or Read WSE Keys in Windows Server 2003

Recently we tried to build an older version of a system that is hosted on Windows 2003 server. Every project in the build would fail with Visual Studio unable to sign the DLL. Of course the DLL was not there in the target folder, but that was not the problem. The build had worked before we rebuilt the server.

A Google search produced a suggestion to change the file permissions on: >

C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys

We gave Administrators full access to the folders and were able to compile.

Just a couple of days earlier, I had to give IIS_WPG access to this folder in order for a web service to be able to read the keys and decrypt a WSE message. The service had worked fine after I had installed it and given access to the key file using the WSE key utility.

I noticed a pending Windows update icon was no longer in the system tray. It appears that update had tightened security on that folder.

ASP.Net Scroll on Post Back

A recent project I’ve been working on is a knowledge base with dynamic data structures. As such, all of the controls placed on the update page are dynamically created at runtime.

There is a drop down list for country and state, with the country list causing a post back to fill the state list. We needed the page to scroll back to the country list after postback to relieve the user of having to scroll down two pages.

It didn’t take much research to find the Page directive MaintainScrollPositionOnPostback=”true”. While this did get the page to scroll back to the drop down list, it also caused the page to scroll back to the submit button when there was a validation error (all done on the server side). Hey, no problem, I’ll just set that directive programatically only when the post back is caused by that drop down list.

I found a nice article by Peter Bromberg on a way to determine the control that causes a post back at http://www.eggheadcafe.com/articles/20050609.asp. I got null exceptions when trying to return the control, so I changed the code slightly to just return a string that describes the type of control.

But more null exceptions awaited me when I tried to set the scroll back directive from the drop down list call back function. Apparently this was also related to the dynamic nature of the page. I decided to abandon the page directive and to wade into Javascript.

An article at ASP.Net magazine by Brad McCabe, http://www.aspnetpro.com/NewsletterArticle/2003/09/asp200309bm_l/asp200309bm_l.aspgot me started in the right direction. The functions presented would save the scroll position in a hidden field, and that field would later be used to set the scroll position. I converted the functions to C# and implemented them in my Page.OnInit function. After getting all the Javascript errors resolved (always a chore for me), the page refused to scroll on post back.

Using a neat JavaScript trace utility from http://www.interlogy.com/~cigdem/trace/I was able to determine that “theBody.scrollTop”, which I translated as “document.body.scrollTop”, was not returning anything. So I was saving nothing to the hidden control.

Looking further I found a set of JavaScript functions by “Tigra” at http://www.softcomplex.com/docs/get_window_size_and_scrollbar_position.htmlthat would find the x or y scroll position, and the height and width of the window. The article also has a great cross reference table for different browsers and the window/scroll functions supported by each.

I only needed the y scroll function, and used a call to it to replace the document.page.scrollTop property. Now Tracer was confirming that I was saving the correct scroll position, but alas the page did not scroll back. After a little more debugging I determined the page would never scroll by setting the document.body.scrollTop in this situation since this is just a property you are setting. Something else would have to read that value to set the scroll. I had no idea what that might be since JavaScript is not my strong point.

But there is a scroll function, and by calling scroll(0, [saved y value]) I was able to finally get the page to scroll back only to the drop down list.

The post back control function, in App_Data/Global.asax.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<pre class="code"><span style="color:blue;">public static string </span>GetPostBackControlType(System.Web.UI.<span style="color:#2b91af;">Page </span>page)
{
<span style="color:blue;">string </span>objectType = <span style="color:blue;">string</span>.Empty;
<span style="color:#2b91af;">Control </span>control = <span style="color:blue;">null</span>;
<span style="color:blue;">string </span>ctrlname = page.Request.Params[<span style="color:#a31515;">"__EVENTTARGET"</span>];
<span style="color:blue;">if </span>(ctrlname != <span style="color:blue;">null </span>&amp;&amp; ctrlname != <span style="color:#2b91af;">String</span>.Empty)
{
control = page.FindControl(ctrlname);
}
<span style="color:green;">// if __EVENTTARGET is null, the control is a button type and we need to // iterate over the form collection to find it </span><span style="color:blue;">else </span>{
<span style="color:blue;">string </span>ctrlStr = <span style="color:#2b91af;">String</span>.Empty;
<span style="color:#2b91af;">Control </span>c = <span style="color:blue;">null</span>;
<span style="color:blue;">foreach </span>(<span style="color:blue;">string </span>ctl <span style="color:blue;">in </span>page.Request.Form)
{
<span style="color:green;">// handle ImageButton controls ... </span><span style="color:blue;">if </span>(ctl.EndsWith(<span style="color:#a31515;">".x"</span>) || ctl.EndsWith(<span style="color:#a31515;">".y"</span>))
{
ctrlStr = ctl.Substring(0, ctl.Length - 2);
c = page.FindControl(ctrlStr);
}
<span style="color:blue;">else </span>{
c = page.FindControl(ctl);
}
<span style="color:blue;">if </span>(c <span style="color:blue;">is </span>System.Web.UI.WebControls.<span style="color:#2b91af;">Button </span>||
c <span style="color:blue;">is </span>System.Web.UI.WebControls.<span style="color:#2b91af;">ImageButton</span>)
{
control = c;
<span style="color:blue;">break</span>;
}
}
}
<span style="color:blue;">if </span>(control != <span style="color:blue;">null</span>)
{
objectType = control.GetType().ToString();
}
<span style="color:blue;">return </span>objectType;
}</pre>

The JavaScript placed in the body of the aspx page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<pre class="code"><span style="color:blue;">&lt;</span><span style="color:maroon;">script </span><span style="color:red;">type</span><span style="color:blue;">="text/javascript" </span><span style="color:red;">language</span><span style="color:blue;">="javascript"&gt; function </span>SaveScrollLocation() {
<span style="color:#006400;">//trace('Scroll Top = ' + f_scrollTop()); </span>window.document.forms[0].__SCROLLLOC.value = f_scrollTop();
}
window.document.body.onscroll = SaveScrollLocation;
<span style="color:blue;">function </span>f_scrollTop() {
<span style="color:blue;">return </span>f_filterResults(
window.pageYOffset ? window.pageYOffset : 0,
document.documentElement ? document.documentElement.scrollTop : 0,
document.body ? document.body.scrollTop : 0);
}
<span style="color:blue;">function </span>f_filterResults(n_win, n_docel, n_body) {
<span style="color:blue;">var </span>n_result = n_win ? n_win : 0;
<span style="color:blue;">if </span>(n_docel &amp;&amp; (!n_result || (n_result &gt; n_docel)))
n_result = n_docel;
<span style="color:blue;">return </span>n_body &amp;&amp; (!n_result || (n_result &gt; n_body)) ? n_body : n_result;
}
<span style="color:blue;">&lt;/</span><span style="color:maroon;">script</span><span style="color:blue;">&gt; </span></pre>
**The C# snippit placed in the Page OnInit function:**
<pre class="code"><span style="color:green;">// hidden field to store scroll location </span><span style="color:#2b91af;">Page</span>.ClientScript.RegisterHiddenField(<span style="color:#a31515;">"__SCROLLLOC"</span>, <span style="color:#a31515;">"0"</span>);
<span style="color:blue;">if </span>(<span style="color:#2b91af;">Page</span>.IsPostBack)
{
<span style="color:blue;">string </span>ctrlType = <span style="color:#2b91af;">Global</span>.GetPostBackControlType(<span style="color:blue;">this</span>);
<span style="color:blue;">if </span>(ctrlType.Contains(<span style="color:#a31515;">"DropDownList"</span>))
{
<span style="color:green;">// Keep scroll at the country drop down list </span>SetScrollLocation();
}
}</pre>

The C# function to Set the Scroll Location:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<pre class="code"><span style="color:blue;">private void </span>SetScrollLocation()
{
<span style="color:#2b91af;">StringBuilder </span>jsSB = <span style="color:blue;">new </span><span style="color:#2b91af;">StringBuilder</span>();
jsSB.Append(<span style="color:#a31515;">"&lt;script language='javascript'&gt;"</span>);
jsSB.Append(<span style="color:#a31515;">"function SetScrollLocation () {"</span>);
jsSB.Append(<span style="color:#a31515;">" scroll(0," </span>+ Request[<span style="color:#a31515;">"__SCROLLLOC"</span>] + <span style="color:#a31515;">");"</span>);
jsSB.Append(<span style="color:#a31515;">"}"</span>);
jsSB.Append(<span style="color:#a31515;">"window.document.body.onload=SetScrollLocation;"</span>);
jsSB.Append(<span style="color:#a31515;">"&lt;/script&gt;"</span>);
Page.ClientScript.RegisterClientScriptBlock(Page.GetType(), <span style="color:#a31515;">"setScroll"</span>, jsSB.ToString());
}</pre>

While this implementation only acts on the DropDownList, you could easily extend it to different controls or different dropdowns by returning the ID of the control causing the postback.

Using WSE 2.0 in ASP.Net 2.0

I was tasked with developing a web service for our development team that would mimick the operation of a client web service that has access limited by IP address. This target service was developed with WSE 2.0. The encryption used in WSE 2.0 is different from that in 3.0. Allthough some posts I read said you could change the encryption mode, I decided it would be less risky to adapt WSE 2.0 to ASP.NET 3.0.

There are lots of sample programs for using WSE 3.0 in ASP.Net 2.0, but none using WSE 2.0. Here are a couple of things that I had to change in the Web.Config given in the sample projects that come with the WSE install:

1
2
3
4
5
6
7
8
9
10
11
12
<security>
<x509 storeLocation="LocalMachine" allowTestRoot="true" allowRevocationUrlRetrieval="false" verifyTrust="true"/>
<!--Replaced UsernameSignCodeService with App_Code for using WSE 2.0 in VS2005!!!! -->
<securityTokenManager type="EcsMockWebService.CustomUsernameTokenManager, App_Code" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" qname="wsse:UsernameToken" />
</security>
<webServices>
<soapExtensionTypes>
<!-- group="0" removed from the end of the following line to use WSE 2.0 in ASP.Net 2.0 -->
<add type="Microsoft.Web.Services2.WebServicesExtension, Microsoft.Web.Services2, Version=2.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1"/>
</soapExtensionTypes>
</webServices>

Date Format Recognition in ASP.NET Controls

To make an ASP.NET control validator recognize an alternate date format such as DD/MM/YYYY, set the culture at the page level with a command like this:

Page.Culture = “en-AU”;

This will set the validators for Australian English, and the following validator will work correctly:

1
2
3
<asp:CompareValidator ID="cvStartDate" runat="server" ControlToValidate="txtStartDate"
ErrorMessage="The From Date should be in the format DD/MM/YYYY." Operator="DataTypeCheck"
Type="Date">*</asp:CompareValidator>

CSS and ASP.Net

We were recently handed some mock up ASPX pages to use in a facelift of an existing ASP.Net 2.0 application. The pages used CSS extensively and unfortunately used no server controls as in the existing application. The conversion has been painful and time consuming.

Apparently label controls that have no text play havoc with the format, large blank areas will appear when you narrow the browser window. I had to change them to be not visible and add code to switch the visible attribute on and off. This rendered useless my long time use of an informational label with view state disabled, which never needed clearing or making not visible.

Also some controls such as the checkbox are rendered with a SPAN around them that causes unwanted wrapping problems. I tried to have the created SPAN’s inherit a style, but this would not fix wrap problems no matter how wide I made the span width.

It is interesting to note that checkbox controls that are dynamically added to a panel do not have this problem. Of course then you have to maintain the view state of the control yourself which adds a lot of complexity.