Thursday, November 27, 2008

Reverting Back to Visual Basic 6

Though I spend most of my life these days working with C#.net (and absolutely love it by the way!) occasionally I get a request from a long-standing client to update an old WinForms app. I built the first version in 1995 with Visual Basic 1.0 for DOS. I so wanted to write it in VB 3.0 for Windows but they insisted that they were more comfortable with DOS apps.

So build it I did. It was so primitive that I distinctly recall having to manually construct a ListBox!

The entire project will always be memorable to me. This was my first contract after going into business for myself for the first time on January 1, 1995. Just a few weeks before I had purchased a brand spanking new Pentium 1 / 90 MHz computer. With a monitor and printer the whole thing came to about $5,000. Yes, five THOUSAND dollars, not five hundred!

To start off the project I drove from Vancouver all the way up to Thompson, Manitoba, a distance of about 2500km.
I made this journey (and many subsequent ones like it) in my trusty 1987 VW Diesel Golf. I loved that car. On a tank of diesel fuel, which then cost about $25.00 I could drive 800km. I bought it in Ontario in 1992 with 60,000 km on it. It would last until 2001 with over 300,000 km. By then the driver's seat was literally ready to fall through the much decayed floor! So I donated it to a high school in Victoria, BC and received a tax credit for $100. Apparently they were going to cut it in half and weld it onto the other half of another VW.
Getting back to the original story . . . I spent a few weeks up in Thompson, gathering user requirements for the Survey System application (as in cartographic surveying) that I had been hired to build. I then drove back to Vancouver and started work on it. I got the entire thing built in about 2 months and returned to Thompson to install it, test it, tweak it, and train the users. This all went exceedingly well.

I recall that I was only charging them $35 per hour (which was even very cheap back then) for a grand total of less than $15,000. At the end of the project my sponsor, a wonderful English gentleman named Noel Laine, said to me, "I don't know how to tell you this, Rob, but you didn't charge us enough for this!" I asked him if I could retroactively raise the price. :-) He smiled and slowly shook his head.

It was a great start to my career as a consultant though and I have no regrets. And I believe my hard work and dedication laid the groundwork for a longstanding, positive relationship with this client. The fact is that in the summer of 1997 they paid me to upgrade it to work with Windows. I think I charged them $50 per hour for a total of $20,000; still cheap by today's standards! What was great, and a credit to Microsoft, was that the code for the core calculation module could be brought directly into Visual Basic 5.0, which was the standard at the time. It was very involved, intricate programming, involving a lot of math and geometry, which I hope I'll never have to write again!

Since that time, there have been periodic requirements every few years to add a feature and tweak something else. Just yesterday I received such a request. Switching my mind from the elegance and strongly typed language of C# to the much less strongly typed and error prone Visual Basic 6.0 took a few minutes but I quickly got the hang of it.

The IDE of Visual Studio .Net is so much more intelligent than the IDE of Visual Basic 6. I really missed not being immediately informed of coding mistakes I was making. To find the obvious ones, I had to compile the code into an EXE. Only then would it point out things like "If with no End If" and "Variable not declared". These are all things that a modern developer takes for granted.
When I finally got things working & tested, I uploaded a copy of the new version to an FTP server and informed my client of the same. He immediately downloaded it this morning and tested it. There was one minor tweak which I've already fixed and re-uploaded. No more driving 2,500 km or having to send a disc (or disks!) in the mail.

How the world has changed in such a short period of time. Yet it's always fun to walk down memory lane!

Friday, November 21, 2008

Passing a Method as a Parameter

I have a library that has a number of methods similar to this:

public static DataTable GetContracts()
{
string tableName = "Contracts";
DataTable dataTable = (DataTable)Tools.GetCacheObject(tableName);
if (dataTable == null)
{
dataTable = DataObjects.Common.GetContractsFromDB();
Tools.AddToCache(tableName, dataTable);
}

return dataTable;
}


The only things that change in each one are the items shown in purple. I got to thinking that this code could be streamlined further by simply passing in 'tableName' as a parameter. But what to about the fact that the method being called in 'DataObjects.Common' changes too?

Well, thankfully C#.net has a solution for this too! And its name is "Delegate". It essentially acts as a proxy for a method, allowing a method to also be passed in as a parameter.

Here's the simple code to make this work, using the same example as above:

private delegate DataTable DBFetchDelegate();

private static DataTable GetDataTable(string tableName, DBFetchDelegate dbMethod)
{
DataTable dataTable = (DataTable)Tools.GetCacheObject(tableName);
if (dataTable == null)
{
dataTable = dbMethod();
Tools.AddToCache(tableName, dataTable);
}

return dataTable;
}

public static DataTable GetContracts()
{
return GetDataTable(
"Contracts", DataObjects.Common.GetClosedMonths);
}

Wednesday, November 19, 2008

Obtaining the largest ID value from a DataTable

In the dotNet Framework data tables are often used to represent tables from a database. And in these there's often an "ID" column. A common desire is to obtain the largest ID value in the data table. I did some searching and couldn't find a direct answer. So I experimented and came up with a very simple solution:

public static int GetMaxID(DataTable dataTable)
{
// Sort the DataRow Array by descending ID
DataRow[] sortedRows = dataTable.Select(null, "ID DESC");

// and return the first ID, which will be the maximum
return (int)sortedRows[0]["ID"];
}

Better Understanding of State Management in ASP.Net

For some time I've been using all of these special ASP.Net objects to assist with state management:
  • Cache
  • Application
  • Session
Up until now though, I was misinformed about something. I had thought that when I retrieved a complex object out of one of these state management objects that I was getting a copy of it. In this way, I could do some work on the "temporary object" but then had to save it back in order for the change to be permanent.

This is absolutely incorrect!

So, for example:

DataTable dtEmployees = (DataTable)Session["Employees"];
DataRow row = dtEmployees.Rows[5];
row["FirstName"] = "Steve";

I do NOT have to use the following line:

Session["Employees"] = dtEmployees;


Rather, right after the name was changed above, I could have done this:

((DataTable)Session["Employees"]).Rows[5]["FirstName"]

and "Steve" would be returned!

Thursday, November 13, 2008

Dynamically Modifying Web.Config's MailSettings

Most modern ASP.Net websites need to send e-mails. One reason is that the Password Recovery control within the Memberships & Roles infrastructure needs to send the user an e-mail with a temporary password when he forgets his.

Typically, when e-mails are sent, this section of web.config is examined for SMTP credentials:

<system.net>
<mailSettings>
<smtp>
<network host="shawmail.vc.shawcable.net" password="" userName="" />
</smtp>
</mailSettings>
</system.net>

Note: The credentials shown above are just examples

If your site is only going to be run from one server then it's perfectly fine to manually edit this section in web.config and never think about it again.

But I have a situation where a website I'm developing is going to be deployed from multiple servers to different audiences, each managed by a separate Website Administrator. The skill levels of Web Admins vary greatly, so I really don't like them fiddling deep inside the web.config file.

Instead, I've setup some special application variables that are defined near the top of web.config:


<appSettings>
<add key="SMTPserver" value="<span style=">mySMTPserver"/>
<add key="SMTPusername" value="<span style=">smtpUsername"/>
<add key="SMTPpassword" value="<span style=">smtpPassword"/>
</appSettings>

Notice how this trio directly matches with the previous set. I needed to find a way to allow the web admin to modify these app variables and have the changes reflected in the other part of web.config.

I found the solution here. For my app I added this code to Global.asax:

void UpdateMailSettings()
{
bool modified = false; // Default assumption

// Retrieve the current 'web.config' file
System.Configuration.Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");

// And within it, get the 'mailSettings' section, if it exists
System.Net.Configuration.MailSettingsSectionGroup mailSettings = (System.Net.Configuration.MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings");

string server = BusinessObjects.Tools.GetAppSettingsValue("SMTPserver");
if (mailSettings.Smtp.Network.Host != server)
{
mailSettings.Smtp.Network.Host = server;
modified = true;
}

string username = BusinessObjects.Tools.GetAppSettingsValue("SMTPusername");
if (mailSettings.Smtp.Network.UserName != username)
{
mailSettings.Smtp.Network.UserName = username;
modified = true;
}

string password = BusinessObjects.Tools.GetAppSettingsValue("SMTPpassword");
if (mailSettings.Smtp.Network.Password != password)
{
mailSettings.Smtp.Network.Password = password;
modified = true;
}

if (modified)
config.Save();
}

It works great! Be aware that whenever web.config is modified, the application is restarted. But because this code is executed right at the beginning, web.config is only modified once - if necessary - and then never again.

In the future I will likely create a user-friend Admin screen within the app to allow them to modify all the editable application variables from there.

Wednesday, November 5, 2008

Obtaining the Current User from ASP.Net's Membership Infrastructure

The extensive membership (& roles) library available to ASP.Net developers is very powerful but also bewildering at times. Case in point is how to obtain the current username. Having just implemented it in a sophisticated multi-user app, I've been forced to really put it to the test.

Depending on the situation, the current username can be obtained in these different ways:
  1. Login1.Username
  2. Page.User.Identity.Name
  3. HttpContext.Current.User.Identity.Name
Why the need for all these different approaches to get the same data? Well, here are some examples:
  • If you need it in the "Logged_In" event handler, #2 & #3 aren't accessible.
  • If you need it outside of your Login page, #1 isn't accessible.
  • If you need it outside the context of a Page, such as when an asynchronous AJAX event is fired, #2 isn't accessible.
So flexibility is the key here. And knowledge of the different paths to the same answer is more than a little useful!

Monday, November 3, 2008

Using an External Javascript File with ASP.Net

I came across a very strange problem and a solution that I think others will benefit from.

To make things more modular I've started placing all commonly used Javascript functions into an external ".js" file. When using AJAX, the only thing you have to do to define each such file in the ScriptManager:

But here's the weird thing: Say you have a Javascript function called "abc" setup and working. If you then decide to rename it to something else (perhaps something more descriptive) you'll discover that you get an error, even when you've rebuilt your solution.

The reason is because these Javascript functions are cached in your browser, not in Visual Studio. So you need to clear your cache, which is often referred to as deleting all temporary files. Once you do that then your app will be able to find the newly named Javascript function.