# Monday, September 15, 2008

Replacing Notepad with Notepad2

Notepad2 is great - no arguments.  It was only this morning I actually got around to 'replacing' the standard notepad.exe with notepad2.exe.  You'd think it was just a simple rename, but Omar Shahine shows the way...

posted on Monday, September 15, 2008 8:36:04 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]

Using CSS Frameworks to do the heavy layout lifting

CSS is great, until you're faced with a deadline and a broken IE6 layout.  You get the cowboy gear out and - 'Welcome to Hacksville!'  

There's been a growing trend of CSS frameworks recently as people are obviously crying out for 'something' to save them from the madness. 

I'm trying out the 960 grid system at the moment, as it's not too prescriptive and gives me the flexibility I need.

There's a great review of the best frameworks on adactio.  I'm sure there's one to suit almost every need.  Remember they're just the starting point if you're doing serious design, but they'll take most of the pain away, because the heavy 'hack' lifting is done for you...   

Also - more on Wikipedia (Update: 17/09) - BlueTrip's the way to go - best of several worlds - nice.

posted on Monday, September 15, 2008 8:30:41 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Friday, August 15, 2008

Issues with data binding, Typed Datasets, DataRows and Null Values

Had some fun this morning as we're trying to get a simple framework set up for a web app, that others can understand, using out-of-the-box functionality if possible.  I tried to avoid the normal tendency to put the 'architecture hat' on and overcook it.  If you can't be bothered to read all of this then just scroll to the bottom for the 'take home' on binding against typed datasets with null values.

We started off the prototype with a typed Dataset (including a couple of DB tables, and key relationships between each), a table adapter for each table, and a DetailsView at the front end, bound to an objectdatasource, which in turn talks to a business object that calls the TableAdapter.  That's a lot for one sentence so from top to bottom...

DetailsView --> ObjectDataSource --> BusinessService --> TableAdapter --> Database

This all sounds pretty straightforward, and it is - unless (as in this case) you decide to have your business object pass back a single (strongly typed) DataRow for your front end to bind against.  This seemed reasonable at the time because I only ever want to show one row here.

You'll basically get a StrongTypingException for every null field you try and bind, because by default a null field value will raise an exception when accessed through its generated property. 

We got distracted for a while exploring this as the natural tendency is to think 'ah - I'll make sure it doesn't raise the exception' - treating the symptom rather than the cause.

It turns out that any non-string field in a typed dataset table can't have a default of null (or anything other than 'raise exception').  If you stuff in a _null value directly in the XSD your fields will disappear from the public properties (rather curiously) when you compile - so that's not an 'enterprise' option.

I tried changing the return type in my business method to a generic DataRow to try and force the data binding to NOT use the strongly typed properties (getting warmer but still no dice.  I knew I was missing something really simple...

I then stumbled across a post (bottom of the page) that illustrated databinding will use different methods to bind based on what interfaces it finds.  The binding mechanism basically has no choice but to use the strongly typed public properties on a strongly typed or generic DataRow, as there's no way for it to successfully enumerate the values.  If you bind against almost anything else you'll most likely have no issues (it appears the databinding doesn't rely solely on ITypedList).  We're now binding against a typed DataTable (rather than generic DataView) and all's fine.  If you want (rather) more info on ITypedList then look here.

So...

Don't databind against a typed (or untyped) DataRow!

posted on Friday, August 15, 2008 1:45:43 PM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [1]
# Wednesday, August 06, 2008

12 Usability Tips for Agile Development

Useful reference material if you're trying to get a usability focus in your agile projects

http://www.uie.com/articles/best_practices

http://www.uie.com/articles/best_practices_part2

and a bit more from Smashing Magazine

posted on Wednesday, August 06, 2008 10:03:40 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]

Setting up a Continuous Integration .NET Build Server without Visual Studio

We start with Windows XP SP2 (doesn't really need to be a 'server').  My requirement is to rely on the .NET framework and open source tools - i.e. not requiring a Visual Studio Licence.  We're using .NET 2.0 so everything will be in reference to that.

I assume here that you'll be familiar with the actual software below and just want to get a Build Server up and running without having to install Visual Studio.  If you're not familiar with Continuous Integration then start by looking at Martin Fowler's Continuous Integration article and then the info on CruiseControl.NET, as that's the tool that pulls everything together. 

There's help on each of the sites below for installing and using each of the tools, so I won't go into detail about each one.  The order of the list isn't critical, but you'll probably have some issues unless all are done.

  1. Install IIS from Add/Remove Windows Components (used for CruiseControl.NET) if not already present
  2. Open up a command prompt and run C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -i (to install ASP.NET in IIS)
  3. Install CruiseControl.NET (1.4) from http://confluence.public.thoughtworks.org/display/CCNET/Download.  NOTE:  If you're using a remote SubVersion repository (as in my case) then you'll need to run the CruiseControl.NET Service as a domain user that has access to the network location of the repository  rather than LocalSystem.  This is because CruiseControl will be using the SVN client to poll for changes. 
  4. Install Microsoft .NET Framework 2.0 SP1 from http://www.microsoft.com/downloads/details.aspx?familyid=029196ed-04eb-471e-8a99-3c61d19a4c5a&displaylang=en
  5. Install latest SubVersion (1.5) from http://www.collab.net/downloads/subversion/
  6. Install appropriate NUnit from http://nunit.org/index.php?p=download (targeting .NET framework 2.0 - I have 2.4.6)
  7. Install latest FxCop from http://www.microsoft.com/downloads/details.aspx?familyid=3389F7E4-0E55-4A4D-BC74-4AEABB17997B
  8. Install .NET Framework 2.0 SDK from http://www.microsoft.com/downloads/details.aspx?familyid=fe6f2099-b7b4-4f47-a244-c96d69c35dec&displaylang=en (you'll need this to get around a NAnt bug whereby it can't resolve an internal property to load the .NET framework)
  9. Download latest NAnt from http://nant.sourceforge.net/
  10. Download latest NAntContrib from http://nantcontrib.sourceforge.net/ (You'll need this to run MSBuild)
  11. To save getting errors when building Web Projects (and other types) the easiest thing is to copy c:\program files\msbuild from your dev machine to the build server (otherwise you'll have to alter the path of where the targets are in each project).

Once you've got all of these set up then you'll be able to add some builds to your ccnet.config file ( most likely c:\program files\CruiseControl.NET\server\ccnet.config)

Test CruiseControl's happy by going to http://localhost/ccnet It should come up OK but basically show 'no projects'.

I'm not going to go into detail about setting up the builds here but may cover that in a follow up article, as it requires some more setup with SVN and a 'standard' project structure (to get benefit from reuse of build scripts).  If you're still with me then I'll share one last thing which might help... 

Some people choose to have NAnt in a standard place and just reference it from the PATH, but I now use the power of svn:externals to drag in NAnt, NUnit and other common external dependencies like MS Enterprise Library from a shared SVN location to each project.  This means you just get latest of a project and it has 'everything' needed to build - no installations or assumptions about locations of tools.

posted on Wednesday, August 06, 2008 8:35:10 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Wednesday, July 30, 2008

Microsoft Guidance Explorer - An alternative to WikidPad?

I tried Guidance Explorer again after a good while (for the purposes of documenting standards and processes), and concluded that it wasn't going to be up to the job because printing wasn't supported. 

I've been using WikidPad for a while and have been struggling with it's usability so I had another google around the printing capability of Guidance Explorer (my eventual medium is likely to be some other wiki-like product, so this is likely to just be my 'input medium').  I found the (rather obvious) Guidance Explorer Overview on Codeplex that explains that printing is indirectly supported through the export formats. I tried it and - whoa!! I take it all back. 

The export to Word format at different levels and different views is just great.  It gives you an index at the top with links to the actual content.  The formatting's nice too.  The HTML format's just the same as the Word doc visually, but spits out the content to separate files which is a bit nicer for deployment.

I still can't believe how little-known Guidance Explorer is, but much of the Patterns and Practices work is documented, along with many other resources.  I'll certainly be saying Bye bye to WikidPad.


posted on Wednesday, July 30, 2008 10:14:58 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Monday, July 28, 2008

Favicon Browser Support Chart. How different browsers support the Favourites Icon

At last I've found a good reference on which browsers support which types of icons/png's etc for the Favicon.  I guess the news is that you need more than one approach if you're wanting to serve a favicon to the most potential users.

posted on Monday, July 28, 2008 9:16:08 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Wednesday, July 23, 2008

Upgrading SubVersion Server and Repositories to 1.5

After upgrading TortoiseSVN and AnkhSVN to SubVersion 1.5, I set about upgrading the server this morning.  The main reason for this is that I wanted to merge a branch back into the trunk using the integrated merge option, and this simply doesn't work with a 1.4x server and repository.  Everything needs to be 1.5 for that to work. 

We're currently on 1.4.5 ish, from a previous 1-click Windows/Apache setup with some mods for groups authentication

I just downloaded the 1.50 server Windows binary and it actually now does a nice thing of asking whether you want to use SvnServe or Apache. 

I had some issues originally running Apache 2.2 with 1.4, and ended up with Apache 2.0.x but gave the new 1.5 install a go.  The following is how I upgraded my install from 1.4x using the same httpd.conf settings from the previous installation (link above).

Upgrading the Server

  1. Stop existing services (Apache etc)
  2. Back up your httpd.conf (my original install was apache 2.0 and went into a different folder) - but you'll need the settings in any case
  3. Back up mod_auth_sspi.so from your existing Apache installation (/modules folder).  If you were using Apache 2.0 then don't bother 'cos it won't work with 2.2.
  4. NOTE: If you want to upgrade your repository (required to use new 1.5 merging functionality) you need to first dump out the existing repository using the 'old' installation
    1. Backup the existing repository
    2. Run svnadmin /repos/path > dumpfile.txt
    3. You do the rest once you've upgraded the server
  5. Install 1.50 server (choosing Apache and whatever port / repository location you previously had.  You'll replace the repository location later, but make sure your port's right to save confusion and updates to httpd.conf).
  6. Edit the new httpd.conf replacing all '<Location>' sections at the bottom with that from your saved config.
  7. Paste the following line at the bottom of the LoadModule section
    LoadModule sspi_auth_module modules/mod_auth_sspi.so
  8. Copy an Apache 2.2-compatible mod_auth_sspi.so into the /modules folder.  As noted above if you take mod_auth_sspi.so that was compiled against Apache 2.0 you'll get a message along the lines of:
     
    httpd: Syntax error on line 117 of C:/Program Files/CollabNet Subversion Server/httpd/conf/httpd.conf: API module structure 'sspi_auth_module' in file C:/Program Files/CollabNet Subversion Server/httpd/modules/mod_auth_sspi.so is garbled - expected signature 41503232 but saw 41503230 - perhaps this is not an Apache module DSO, or was compiled for a different Apache version?
     
    If you go to the home of mod_auth_sspi you'll only find a module compiled against 2.0 so after hunting around a bit I found a copy compiled against 2.2.  Just copy the .so file into your modules folder)
  9. Start her up, and hopefully hey presto, you'll have 1.50 server.  You can verify this easily by loading up the repository url in your browser and the footer should say something like... Powered by Subversion version 1.5.0 (r31699).

Upgrading the repository

  1. With the new version (1.5) binaries run:
    1. svnadmin create /temp/repos/path
    2. svnadmin load /temp/repos/path < dumpfile.txt
  2. Swap the old and new repository folders (useful to keep the backup)
  3. You're all up to date.
posted on Wednesday, July 23, 2008 3:10:18 PM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]
# Tuesday, July 22, 2008

Refactoring the inefficient data loop

OK. Last one for today.  I'm not going to go into too much detail except to say that the offending piece of code loads all items in a 'matrix' database table and builds a business object collection hierarchy.  This table has 4 fields, one or more of which may be NULL - for different meanings.  I really don't particularly like this type of approach but changing it right now would involve quite a bit of work.

The table looks something like

Level1ID
Level2aID
Level2bID
Level2cID

This ultimately translates to a business object hierarchy of:

 

  • Rows with Level1ID (only) - all other fields null - are Level1 objects
  • Rows with Level1ID and Level2a ID (only) are Level2a objects

etc...

 

The matrix table just contains the primary keys for each object so the code loads each one in the style below...

 

Load matrix
for each row in matrix
{
 if row type == Level1
     Add to Level1Collection
 else if row type == Level2a
 {
  Load Level2a object
  Add Level2a object to Level1[Level1ID].Level2aCollection
 }
 else if row type == Level2b
 {
  Load Level2b object
  Add Level2b object to Level1[Level1ID].Level2bCollection
 }
 else if row type == Level2c
 {
  Load Level2c object
  Add Level2c object to Level1[Level1ID].Level2cCollection
 }
}

 

This seems reasonable enough (logical anyway) given the way the data's being retrieved.

This does however load another several hundred rows from more stored proc calls that load each new object into the child collections.  This whole thing consistently takes around 8 seconds.

 

The Refactoring

 

A wise man once told me that if you can be sure the bulk of your operation is data access then lump it all together if you can, and do the loop or other grunt processing on the client. 

  1. I created a new Stored Procedure to return 4 result sets.  These come back as 4 tables in the target DataSet.  Each resultset is qualified by the same master criteria, and just uses joins to get a set of data that can be loaded directly into the collections.  The original stored proc is no longer required and this is now the only data access call.
  2. I changed my collection classes slightly to allow a LoadData method which takes in a dataset, a tablename and a parent key.  This means I can add Level2a objects to the appropriate Level1 collection.  The pseudo code now looks like...

Load multiple results
for each row in matrix
{
 if Level1 Results present 
   LoadData on Level1Collection
    
 if Level2a Results present
  for each Level1 row
   LoadData on Level1[Level1ID].Level2aCollection
   
 if Level2b Results present
  for each Level1 row
   LoadData on Level1[Level1ID].Level2bCollection

 if Level2c Results present
  for each Level1 row
   LoadData on Level1[Level1ID].LevelcbCollection
 
}

As I said at the beginning, there are some definite improvements to be made from changing the data structure, and a lot of this code could look a lot nicer by using Typed Datasets with relationships defined.

 

The new approach actually completes in less than 100ms.  I couldn't actually believe it myself, and made sure I killed connections, cache etc to make sure the database was coming in cold.  Still the same.

 

This basically proves that for data-heavy operations, things really start to hurt when you're making repeated client round-trips, however small the call.  This is basically a 99% saving in load time for this business object. 

 

The resulting page is also really snappy now and I'm sure the customer won't even notice :-)

 

posted on Tuesday, July 22, 2008 5:15:02 PM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]

Refactoring the blindingly obvious - Enums and Value Names

I know many people have written about this one, but it's cropped up yet again in the app I'm maintaining.  The old chestnut of setting a string based on an enum value - when the enum names are identical to the string values (most often with one word names). 


public enum ItemStatus
{
    Draft 
1,
    Pending 
2,
    Released 
3,
    Recalled 
4,
    Rejected 
5,
}


switch (item.ItemStatus)
{
    
case ItemStatus.Draft:
        {
            statusLabel.Text 
"Draft";
            break;
        
}
    
case ItemStatus.Pending:
        {
            statusLabel.Text 
"Pending";
            break;
        
}
    
case ItemStatus.Released:
        {
            statusLabel.Text 
"Released";
            break;
        
}
    
case ItemStatus.Recalled:
        {
            statusLabel.Text 
"Recalled";
            break;
        
}
    
case ItemStatus.Rejected:
        {
            statusLabel.Text 
"Rejected";
            break;
        
}
}


//or alternatively just take the 'name' as a string and get rid of the switch altogether
statusLabel.Text communicationItem.CommunicationItemStatus.ToString("g");


Colorized by: CarlosAg.CodeColorizer

There's other ways to do this if you want more of a 'description' (e.g. multiple words).  The StringEnum class could do it for you, or you could provide your own implementation with a Description attribute on the values - e.g.  [Description("My extended value name")]

posted on Tuesday, July 22, 2008 10:52:28 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]

Refactoring below the surface. Things aren't always as they seem

Continuing the refactoring from last week, I spotted another great example this morning that I 'almost' didn't improve at all.

My 'entry point' into this refactoring was trying to eradicate dynamic SQL and also the dreaded SELECT *, so this snippet popped up in my search.
My immediate reaction was to refactor the dynamic SQL to a Stored Procedure, but then I took a step back and had a further look at the code.

The following has been changed slightly (naming) to protect the innocent. 

string listOfIDs "";
foreach 
(Organisation organisation in dataObject.DistributionList.Organisations.Values)
{
    listOfIDs +
"'" + organisation.OrganisationID + "',";
}
string sql "";
if 
(listOfIDs.Length > 0)
{
    listOfIDs 
listOfIDs.Substring(0, listOfIDs.Length - 1);
    
sql "SELECT * FROM tblOrganisations WHERE OrganisationID IN (" + listOfIDs + ") ORDER BY [Name]";
}
else
{
    sql 
"SELECT * FROM tblOrganisations WHERE OrganisationID = '' ORDER BY [Name]";
}
Database database 
Database.GetDefaultConnection();
DataSet dsOrganisations database.Execute(sql, Database.CommandExecutionType.TSQLDS);
dgrConfirmDistributionList.DataSource dsOrganisations;
dgrConfirmDistributionList.DataBind();

int 
rowCount 0;
foreach 
(Organisation organisation in dataObject.DistributionList.Organisations.Values)
{
    
// Get the datagrid row we are working on

    
DataGridItem dataGridItem dgrConfirmDistributionList.Items[rowCount];

    
// Bind Target Divisions & Select Targeted Divisions
    
Divisions divisions = new Divisions();
    
divisions.Load(dataGridItem.Cells[0].Text);
    
DataGrid dgrConfirmTargetDivisions =
        
(DataGrid) dataGridItem.Cells[2].FindControl("dgrDivisions");
//more.....

 


What this code is doing is actually using an 'already populated' collection of 'Organisation's and performing a nasty dynamic SQL call (using an constructed IN clause) back to the database to retrieve the same data again so it can bind against a datagrid. 

It then goes back to using the original collection further on and does some more binding through the object hierarchy by linking to child controls in the grid.

It's clear here that we've got more going on than the need to just refactor the SQL code (dodgy table names etc).  It turns out that I've touched this code quite recently by refactoring what was a 'pseudo-not-quite' collection into a generic dictionary.  The original poor collection implementation was probably confusing enough to put the developer off trying to use that for data binding.  The collection now can of course be bound against the original collection with no need to go back to the database.

We end up with the following:

dgrConfirmDistributionList.DataSource dataObject.DistributionList.Organisations.Values;
dgrConfirmDistributionList.DataBind();

int 
rowCount 0;
foreach 
(Organisation organisation in dataObject.DistributionList.Organisations.Values)
{
    
// Get the datagrid row we are working on

    
DataGridItem dataGridItem dgrConfirmDistributionList.Items[rowCount];

    
// Bind Target Divisions & Select Targeted Divisions
    
Divisions divisions = new Divisions();
    
divisions.Load(dataGridItem.Cells[0].Text);
    
DataGrid dgrConfirmTargetDivisions =
        
(DataGrid) dataGridItem.Cells[2].FindControl("dgrDivisions");
        
//more.....    

This not only removed bad and unnecessary code that could have hurt performance, it also made me focus on 'real' refactoring opportunities.  It turned out that 90% of the remaining dynamic sql calls we actually duplicates of this code in other ASP.NET pages, and so that could be removed too. 

Refactoring can be quite like peeling an onion.  I'd been in this code a few days ago, and due to a small improvement made then (with the collection), I was able to come back and do some more.  The moral here is 'patience'.  If you're serious about improving all code you touch then you learn to make small, manageable changes that you can test before moving on to the next.  There's still work to do on this application but the code smells are starting to fade.

posted on Tuesday, July 22, 2008 10:14:16 AM (AUS Eastern Standard Time, UTC+10:00)  #    Comments [0]