.Net ramblings
# Wednesday, 18 May 2005
HowTo: set up disk status monitoring (i.e. for a raid array) and send results by email

background

i have a raid 1 array on my server, and i was surprised to see no easy solution to setting up an email alert if one of the disks should fail.  luckily it has never happened but apparently windows doesn't even pop up a task-bar alert if it happens. 

what doesn't work

i tried the Windows performance monitoring and alerts, and while it can tell you the average read/writes per second, it can't tell you how many disks are online.  so i looked into WMI which has an API for hard disks, but i encountered a problem with the API not returning the information it is supposed to for disk status.

enter DiskPart.exe

then i discovered DiskPart.exe, a powerful disk management utility bundled with windows server 2003, i think you can get it for server 2000 too with an admin pack or something. 
you can run DiskPart.exe from the command prompt, type: "Select Disk 0" then hit return, next type "Detail Disk" and hit return.  you should see a list of all the volumes on the disk, along with the disk status "Healthy".
In the case of a raid array, i found it more useful to run the following commands:

select disk 0
select volume 0
detail volume

this outputs all the disks that make up the volume (Disk 0 and 1, in my case), and their status. 
Fortunately, you can pipe the output of DiskPart to a text file, and you can also tell DiskPart to run a list of commands from a script, so the whole thing can be automated as a scheduled task in Windows.  I also wrote a simple c# console app to send the contents of the output file in an email.

The .cmd file i have scheduled contains the following lines:

@diskpart /s c:\Scripts\disk_part_commands.txt > c:\scripts\disk_status.txt
@SendMail server@whatever.ie tim@whatever.ie "Server Disk Status" c:\scripts\disk_status.txt

The SendMail program takes in the following parameters: from address, to address, email subject, text file path.  it is hard coded to use the localhost mail server which you can change if you want in the source code (.Net C#).

Download the send mail console app: SendMail.exe (16 KB)

Download the send mail source code: SendMail.txt (1.26 KB)

Taking it further

if i was really serious about it, i would parse the text file and read in the status directly, and only send the email if the status is something other than 'Healthy', but i think it's nice to get an email once a week anyway from the server, reporting it's disk status and letting you know it is still alive.  if somebody does write a little program to parse the output, you could post it here as a comment, that would be great.

i run the script every week, so in the worst case it could operate for 7 days with a dead drive, and the chance is very slim that the other drive will fail within this time.


Wednesday, 18 May 2005 18:39:12 (GMT Daylight Time, UTC+01:00)  #    Comments [2]  Windows Server

# Wednesday, 11 May 2005
Fix: Forms authentication redirects to a bogus default.aspx page, with RedirectFromLoginPage()

hi,
i've read a lot of posts on microsoft.public.dotnet.framework.aspnet.security about people who ran into problems using forms authentication, and the RedirectFromLoginPage() method, which always redirects to a default.aspx.  this is a big problem if you use sub-folders that don't have a default.aspx page, as in my case.
i read some posts that suggested manually Response.Redirecting the user to the url in the querystring, but actually this is incorrect because Forms Auth puts the default.aspx in that querystring even if the user wasn't at a page called default.aspx. 

i put together a simple solution to get the redirecting to work properly, and am posting it here for future reference:

  • The Login page (Login.aspx) must be set up to read the HTTP_Referrer, and add it to the ViewState in the first Page_Load on that page.
  • In the btnLogin_Click event on Login.aspx, the SetAuthCookie() event should be called, and the user should be Response.Redirected to the referrer value in the viewstate.
  • So you ignore the querystring that Forms Authentication adds on to the Login page.

Here is sample code:


*****************
Login.aspx
*****************

private void Page_Load(object sender, System.EventArgs e)
{
 if(!IsPostBack)
  ViewState["originalUrl"] = Request.UrlReferrer.AbsoluteUri;
}

private void btnLogin_Click(object sender, System.EventArgs e)
{
 string originalUrl = ViewState["originalUrl"];
 if(originalUrl == null || originalUrl == "") // in case the viewstate is corrupt, use default.aspx by 'default'
  originalUrl = "default.aspx";
 
 // do your password checking here
 // if it's all ok then...
 FormsAuthentication.SetAuthCookie(username, false);
 Response.Redirect(originalUrl, true);
}

Wednesday, 11 May 2005 13:33:48 (GMT Daylight Time, UTC+01:00)  #    Comments [0]  Asp.Net

# Tuesday, 26 April 2005
An SQL Query to find duplicate values
SELECT <KEY_COLUMN>, COUNT(*)
FROM <TABLE> GROUP BY <KEY_COLUMN> HAVING COUNT(*) > 1

Tuesday, 26 April 2005 17:00:35 (GMT Daylight Time, UTC+01:00)  #    Comments [0]  Database

# Monday, 04 April 2005
MS Access: using the LIKE function with a parameter
I wanted to do something like SELECT * from MyTable WHERE [Action] LIKE 'Hello%' but using a parameter instead of hard-coding the 'Hello' into the query. using VS and OleDb, the syntax for this wasn't obvious, what finally works is: SELECT * from MyTable WHERE ([Action] LIKE ? + '%')
Monday, 04 April 2005 17:51:14 (GMT Daylight Time, UTC+01:00)  #    Comments [0]  Database

# Sunday, 03 April 2005
MTB: Excellent trail in Limerick - Keeper Hill + Slieve Felim Way

hi,
i went exploring on a new route yesterday and it was by far the most exciting trail i've been on for a long time.  Map and Stats below.

Stats

  • Route length: 76k from Limerick city (Ireland)
  • Time taken: 8 hours including about 7x5-min rests, a half hour nap at the top, and about 10 camera stops.  so you can easily knock an hour and a half of this time.
  • Climb: 700m to Keeper Hill Summit
  • Terrain: About 50km of this trip is road, but it's the fast part and i think the 20k on trails is really worth it.  you could drive to newport and cut out the boring leg between limerick and newport.
  • Water needed: 2.5l energy drink was just about enough for me
  • Food needed: 3 snickers + banana + raisins

Map

Directions

The map has red arrows marking the route i took for the way up, and pink arrows for the way down. In case they don't make sense, here are the english directions:

  • From Limerick, take the N7 dublin road out as far as the roundabout on the new dual carriage way stretch. Take the second exit sign-posted newport.
  • Go straight through newport and keep on the main road and go past the entrance to Tooreenbrien woods. After that, take the next left up a small road.  (marked on map with a red arrow).
  • Keep going and turn right near the end of the road (down a hill) and you should link up with the Slieve Felim way, marked with the hill-walker signs.
  • Then just follow the hill walker signs up and across, and over the hill.
  • There is an extremely fast (and mucky when i did it) descent to Toor (a village apparently).
  • Go right on the main road for almost a kilometer. you get a good view of Keeper Hill from there. Turn left just at the handball court, following the hillwalker signs.
  • The Slieve Felim way doesn't actually take you to the summit, so follow the map to choose one of the paths that leads there.  I had to walk the last 150m because the path was very loose and stoney.
  • I choose the quickest way home once i had reached the top, and it is the fastest descent i've ever been on. the quality of the paths was pretty good when i was there, so i was able to clock 58kph. 
  • you can see with the pink arrows which route i took home.

if you have any corrections suggestions, post your comments below.


Sunday, 03 April 2005 10:23:40 (GMT Daylight Time, UTC+01:00)  #    Comments [1]  Outdoors

# Wednesday, 23 March 2005
SQL left join with a where clause produces strange results

I was doing a straight forward left join to include all the records of a table, regardless of whether they had records in the related table.  However, i added a where clause, which produced very strange results.  Some of the foreign records were not included in the results, specifically those that had no related records at all in the main table.  This was very frustrating to pin down, but i finally found out that the where clause in a left or right join should not use the 'where' syntax.

instead, it should use:

SELECT
    SuperMarketGroups.GroupName,
    SuperMarketGroups.ID, 
    ProductsInGroups.GroupProductCode, 
    ProductsInGroups.MinimumStoreRating, 
    ProductsInGroups.ProductID, 
    (IIf([ProductID] Is Null,'False','True')) AS GroupIncluded
FROM 
    SuperMarketGroups LEFT JOIN 
    ProductsInGroups ON
        (SuperMarketGroups.ID = ProductsInGroups.GroupID 
            AND (ProductsInGroups.ProductID Is Null OR ProductsInGroups.ProductID=?)
        )

note that there is no 'where' clause.  the extra condition is specified with the join columns. this solved my problem perfectly.


Wednesday, 23 March 2005 16:54:28 (GMT Standard Time, UTC+00:00)  #    Comments [4]  Database

Howto: Export a dataset to Excel (c# / asp.net)

In my web applications, i occassionaly need to allow the user to export a dataset as an excel file.  I was using a control written by Prashant Nayak posted on Code Project but he released a new version which was problematic for me, so i looked at other solutions.

obinna igbokwe  from  www.dedicatedsolutions.co.uk posted a good approach which creates a DataGrid object and binds it to the dataset, and then Renders the output of the control to the HttpResponse stream.  This works very well.  I have adapted his code to C# and added an option to specify a filename for the excel file.

Here is the code:

using System;
using System.Data;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Whatever
{
 /// 
 /// This class provides a method to write a dataset to the HttpResponse as
 /// an excel file. 
 /// 
 public class ExcelExport
 {
  public static void ExportDataSetToExcel(DataSet ds, string filename)
  {
   HttpResponse response = HttpContext.Current.Response;
   
   // first let's clean up the response.object
   response.Clear();
   response.Charset = "";
   
   // set the response mime type for excel
   response.ContentType = "application/vnd.ms-excel";
   response.AddHeader("Content-Disposition", "attachment;filename=\"" + filename + "\"");
   
   // create a string writer
   using (StringWriter sw = new StringWriter())
   {
    using (HtmlTextWriter htw = new HtmlTextWriter(sw))
    {
     // instantiate a datagrid
     DataGrid dg = new DataGrid();
     dg.DataSource = ds.Tables[0];
     dg.DataBind();
     dg.RenderControl(htw);
     response.Write(sw.ToString());
     response.End(); 
    }
   }
  }
 }
}

Wednesday, 23 March 2005 16:47:02 (GMT Standard Time, UTC+00:00)  #    Comments [72]  Asp.Net | Database

# Tuesday, 15 March 2005
HowTo: get a random letter in C#
public static char GetRandomLowerCaseCharacter(int seed)
{
   return ((char) ( (short) 'a' + new Random(seed).Next(26)));
}

public static char GetRandomUpperCaseCharacter(int seed)
{
   return ((char) ( (short) 'A' + new Random(seed).Next(26)));
}

If you call the above methods straight after each other with the same seed, you may get the same value on a fast processor.  It is a good reason to pass in a different seed value for operations that will be done in quick succession.  E.g. use DateTime.Now.Seconds for one operation and then use Minutes or Hour or Milliseconds for the next ones.


Tuesday, 15 March 2005 18:26:27 (GMT Standard Time, UTC+00:00)  #    Comments [2]  .Net General

# Wednesday, 09 March 2005
FIX: Crystal Reports error "Logon Failed" happens when viewer control is not in form server tag.

I just spent hours trawling the web trying to find out why my web forms app was giving me the following error:

  • CrystalDecisions.CrystalReports.Engine.LogOnException: Logon failed

I am using a Dataset so there is obviously no logging on necessary for a disconnected data source.  By chance, i noticed that there was no server form around the crystal report viewer control, so i put one in, and it worked.  That has got to be the most annoying error message i have ever come across.


Wednesday, 09 March 2005 16:03:21 (GMT Standard Time, UTC+00:00)  #    Comments [2]  Asp.Net

# Tuesday, 01 March 2005
Regex for a strong password

This came in useful for ensuring that web site users have entered a strong password.  Thanks to Eli Robillard for posting this on his blog.

In this case, a strong password is defined as follows:

  • between 5 and 128 characters long
  • contains at least one digit
  • contains at least one upper case letter
  • contains at least one lower case letter.

Here is the pattern:

^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{5,128}$ 

C# code using this pattern:

public bool IsStrongPassword(string s)
{
    string pattern = @"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{5,128}$";
    return Regex.IsMatch(s, pattern, RegexOptions.IgnorePatternWhitespace);
}

Tuesday, 01 March 2005 18:46:28 (GMT Standard Time, UTC+00:00)  #    Comments [0]  .Net General