Thursday, November 27, 2008
Reverting Back to Visual Basic 6
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
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
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
- Cache
- Application
- Session
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
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.
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.
Wednesday, November 5, 2008
Obtaining the Current User from ASP.Net's Membership Infrastructure
Depending on the situation, the current username can be obtained in these different ways:
- Login1.Username
- Page.User.Identity.Name
- HttpContext.Current.User.Identity.Name
- 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.
Monday, November 3, 2008
Using an External Javascript File with ASP.Net
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.