Monday, December 29, 2008

How to Load an Image from the Internet

I built a simple web page that allows one to listen to radio stations from across Canada:

At first I was loading each station's logo from its parent website. Some were quite large so I had to do a check to determine its dimensions. My first inclination was to use this method:

System.Drawing.Image.FromFile

But I quickly learned that it was only useful for images stored on the local server's hard drive. It wouldn't work for remote ones. To get that working was a bit tricky, but I finally realized this to be the solution:

// Retrieve the actual dimensions of the logo image
System.Drawing.Image actualImage = System.Drawing.Image.FromStream( System.Net.HttpWebRequest.Create(logoUrl).GetResponse().GetResponseStream());

// Keep the image logo to a maximum of 200 pixels tall
if (actualImage.Height > 200)
imageLogo.Width = actualImage.Width * (int)imageLogo.Height.Value / actualImage.Height;

Wednesday, December 10, 2008

ASP.Net Database Publishing Wizard

If you've been developing an ASP.Net DB web app locally and need to publish the DB onto a server that you don't have direct control of, then you're likely going to find it very tricky. Microsoft has built a tool which solves the problem.

The SQL Server Database Publishing Wizard is a free utility that greatly simplifies the process of publishing your DB to a remote server. It asks for a lot of information though, which can sometimes get confusing. I hope this serves as a general guide to help you get up & running with the tool. It goes without saying that the assorted info you see on the screenshots below is mine. You will have to alter all such data for your specific situation.

After an introductory screen, you will see this one. You need to enter the same credentials as you would with the SQL Server Management tool.
You're then prompted to specify which DB you wish to upload (in full or a portion of). By selecting the checkbox at the bottom you will upload the entire DB in its entirety. Note: If you do so, make sure that the size of the DB won't exceed your hosting limits!
If the aforementioned checkbox was not selected then you will be presented with this screen, which allows you to pick what types of objects you want to publish.
Because of what I just selected, I was presented with the next two screens, letting me select the objects I wanted published. Note: For any Stored Procedure you select, it will automatically upload any Tables referred to within, plus their contents. Failure to notice this and you will start uploading huge tables which you never specified on the Tables screen!
Once you've selected what you want published, then you need to inform the tool where to publish it, including all of the necessary credentials. Much has to be entered via the "More..." button. If you're using GoDaddy like I am then finding the precise syntax for the "Target database" was exceptionally tricky. But you can find the info here:
  1. Choose Databases -> SQL Server
  2. On the line specifying your DB, click on the Pencil icon on the right
  3. Click on Configuration
  4. You'll then see all the assorted DB connection info you need
The key thing to remember is that first you need to have the tool login to your provider, then it needs to login to the DB.
There was a short delay before this next screen appeared. I assume that communication with the remote server was what caused the delay. You'll notice that "Publish using transaction" is set to False. I first tried it the other way but a strange error occurred, referring to a mysterious unknown user. I couldn't decipher it. Because Transaction mode was enabled, nothing got published. So I set it to False and though the error occurred again, at least it didn't retroactively remove all the objects it had just created in the DB.
When you first hit 'Next' on the previous screen this next one immediately appears.
Eventually the contents within expand somewhat.
The time it takes is dependent on several factors, but the size of the DB you're publishing will play a large factor. In my case it took about 20 minutes. As long as it seems to be doing something, be patient!

Monday, December 1, 2008

Customizing a Breakpoint in the VStudio IDE

If you're like me, then you frequently setup breakpoints throughout your code to assist you with debugging. Today I had a unique situation where I wanted the code to stop only when a variable acquired a specific value. I looked around the Visual Studio menus but couldn't see anything to help me. So I posted this on an ASP.Net forum. Sure enough, within a few hours a fellow responded, pointing out that one could right-click on a breakpoint and customize it in several different ways. I had absolutely no idea of this! Here are some screenshots:



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.

Wednesday, October 29, 2008

How to Redirect to Login Page after a Session Timeout

In ASP.Net 2.0 applications there's a special event handler called "Session_End" that exists in Global.asax. It is fired when the Session Timeout occurs.

I use this event handler to write key Session variables (all DataTables at the moment) out to a special holding area within the associated database. The next time the user logs in, these same Session variables are restored with the previous data. In this way the user is protected from losing valuable work if they step away from their computer for a moment.

I did some testing and was a little surprised to discover that when such a timeout occurs, the browser just stays on the page it was before. This is clearly a problem, both for security reasons (an unattended corporate app should log itself off) and because the Session variables were now all null. Eventually I heard from a bright developer from New England named Mike Banavige who succinctly explained the situation this way:

Session end does not occur in the context of an http request. It is simply a timeout that occurs on the server. Since there is no request involved in the timeout, there is nothing to redirect.

Put another way, the Session_End event is occurring strictly on the server - the client doesn't know a thing about it. Makes perfect sense - why didn't I think of that!

So what to do with the predicament of the web app remaining on the same page, even though the Session has timed out? Well, more research revealed the answer. Within the Page_Load event handler of my master page codefile, I added this:

Response.AddHeader("Refresh", Convert.ToString((Session.Timeout * 60) + 5));
if (Session.IsNewSession)
Tools.PageRedirect("~/Login/login");


Since every web page in my app utilizes the master page, this Refresh meta tag is inserted into each of them. And because it's in the Page_Load event handler, it gets updated (moved forward) every time a postback occurs. With a typical 5 or 10 minute session timeout, it's highly unlikely that the user would be working on something that long without a postback occurring. It thus serves as an effective way to implement a Logout due to User Inactivity feature.

Monday, October 27, 2008

OnClientClick and Postback

I created a small grid that looks like this:
Intuitively, it makes no sense for the "Discard" button at the bottom to be enabled if none of the checkboxes above it are selected.

After building the JavaScript to handle this logic, I thought I had done everything correctly. But I noticed that everytime I pressed the "Toggle" LinkButton, a postback was performed. Considering that I had no server-side code wired up for this control, I was most confused. I did a little research and discovered that when using the OnClientClick property, you need to explicitly return false or a postback will occur.

Sunday, October 26, 2008

Visible = false vs. display:none

I learned a painful little lesson today which hopefully will benefit others.

Most every ASP.Net control has a Visible property, which can be set to either 'true' or 'false'. If you've ever set it to 'false' and look at how it's rendered, you'll see this HTML property: style="display:none"

But when you're using the AJAX Control Toolkit, the two ways of hiding a control are not always equal.

For example, when I use the ModalPopupExtender to display a dialog box, I frequently define the TargetControlID with a dummy button. Why? So that I can programmatically control when the dialog box is displayed. Here's a very straightfoward example:




The only reason that buttonDummy exists is because the ModalPopupExtender must have a TargetControlID defined. And since you don't want the user to see this dummy button, you need to hide it. But doing so by setting Visible="false" internally seems to mess up the logic of the ModalPopupExtender. When you're expecting the dialog box to display, the code runs but nothing happens.

Sunday, August 31, 2008

Using SetFocus in an AJAX Panel

If you have a TextBox on a regular ASP.net web page then you can force the initial focus to it with code such as this:

SetFocus(textBox);

But if your web page is AJAX enabled then this won't work. But the following code will:

ScriptManager.GetCurrent(this).SetFocus(textBox);

Saturday, May 31, 2008

Turning Off ViewState

ASP.Net's ViewState functionality is an absolutely fantastic feature but is highly overused by many new web developers. The problem with it of course is that the more items that are stored, the more data is returned upon postback. Page sizes getting inflated and often for no particular good reason.

Most of my apps have [at least] these two common pages:
  • About
  • View Error Log
The latter is an Admin-only page that allows me to periodically examine what errors, if any, have been occurring.

In both cases, the pages are very static - ie. display the data and that's it. So there's absolutely no need for any ViewState functionality. Yet on both I observed quite a bit. I don't know precisely where it was all coming from but I didn't want it there. At first I started adding EnableViewState="false" to various ASP.Net controls but then I learned that the quickest and most effective way would be to just add it to the very first line in the markup code, as follows:

<%@ Page Language="C#" MasterPageFile="~/main.master" AutoEventWireup="true" CodeFile="about.aspx.cs" Inherits="about" EnableViewState="false" %>


In my research I learned of a cool tool for deciphering ViewState data into something more meaningful. It's written by ASP.Net guru, Fritz Onion, and you can download it here.

Thursday, May 29, 2008

Less Than Technical Clients

You know the old expression, "the customer is always the boss" ? Well, this story takes that expression to the outer limits ... and then over the proverbial ledge!

The very worst clients are those who have already estimated how long your work should take. In my experience, such conversations often go like this:

Client: "I've been looking at this module and think it should take 10 hours to complete."

Me: "How do you figure that?"

Client - Version 1: "I used to be quite good at building Excel spreadsheets. This data looks a little bit similar to a spreadsheet I put together 7 years ago."

Client - Version 2: "A long time ago [when I was just a code-monkey like you are now] I tinkered with DBase 4. So I know how long these things take to build."

Such comments are made with sincere conviction. These people actually believe that they know as much, if not more, about building software than you do. If such applications are "so simple" to build then why don't they build them themselves? But I've never had the nerve to ask that! question!

Wednesday, May 21, 2008

Integrating FancyZoom into an ASP.Net Website

I've long been searching for an easy, cool way to display full-sized images when one clicks on a thumbnail image. Being an ASP.Net AJAX developer, this isn't always as simple as one would expect, as things sometimes just don't work in this environment whereas they work fine in pure HTML. I'm not a JavaScript guru and so don't find it appealing to have to hack into someone else's code to get it work properly in the ASP.Net environment.

I'd previously tried "LightWindow" and "Yahoo SpryEffects" but just couldn't get them working properly. But my luck changed today! This morning a friend sent me a link to this page. The art project it describes is hilarious but what also caught my attention was the cool way the images were being zoomed when you clicked on them. So I viewed the source code and discovered something called "FancyZoom.js". A quick Google search lead me to FancyZoom.com

Lo and behold, a fellow in nearby Portland, Oregon had built this really neat JavaScript tool. But would it work in ASP.Net?

As I always do nowadays, I start simple. So I created a standalone page to try it out. He recommends installing the two folders, "images-global" and "js-global", in the root folder. I prefer placing all such 3rd Party code in a separate "JavaScript" folder. Doing so, I had to slightly alter the script links. But other than that, everything worked precisely as his instructions laid out:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="Website.Test" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<script src="/JavaScript/js-global/FancyZoom.js" type="text/javascript"></script>
<script src="/JavaScript/js-global/FancyZoomHTML.js" type="text/javascript"></script>
</head>

<body onload="setupZoom()">
<form id="form1" runat="server">
<div>
<a href="Images/sunset.jpg"><img src="Images/sunset_thumb.jpg" alt="" /></a>
</div>
</form>
</body>
</html>


I ran the project and ... it worked! Rarely has that ever happened with JavaScript code I've found on the Internet! This first test was in IE7. I then tested it in Firefox 2.0 and the results were equally great.

The next test was to get it working when a Master Page was involved, which is most often the case with my ASP.Net projects. I added the two "script" lines and the modified "body" line to my "main.Master" file. I thought I might have to add a ScriptManagerProxy control to my content page but I did not. It just worked right away!

But there was one last thing to try. I have a commercial website in development which you can check out here. Most of the images on this site are just static ones that never need to be enlarged. And because the images are not hyperlinked, they won't be affected by FancyZoom. So I could have just activate FancyZoom in the Master Page as before and all would have worked fine. But I wanted to see if I could restrict the scope of the JavaScript to just the one page where I need the zoom facility. That page can be found here.

Getting FancyZoom working here was a little more tricky. At the top of the page I added this:

<%@ Page Language="C#" MasterPageFile="~/main.master" AutoEventWireup="true" CodeFile="screensPPC.aspx.cs" Inherits="screensPPC" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<asp:ScriptManagerProxy ID="ScriptManagerProxy1" runat="server">
<Scripts>
<asp:ScriptReference Path="../JavaScript/js-global/FancyZoom.js" />
<asp:ScriptReference Path="../JavaScript/js-global/FancyZoomHTML.js" />
</Scripts>
</asp:ScriptManagerProxy>

But where to activate the script? Just below the above code I placed this:

<script type="text/javascript">
setupZoom();
</script>


But it didn't work. I'm not 100% sure why but I think it's because the setupZoom code is being executed before the actual images on the client web page are rendered. So I moved the code to right near the very bottom, as follows:

<script type="text/javascript">
setupZoom();
</script>
</asp:Content>

And ... it worked! By the way, in using FancyZoom I replaced all of this:

<asp:imagebutton id="scrnShot1" runat="server" imageurl="~/Images/Screenshots/desktop1_t.jpg" width="200"></asp:imagebutton></span> <span style="font-family:courier new;"> onmouseover="this.style.border='2px solid blue';" onmouseout="this.style.border='2px solid white';"</span> <span style="font-family:courier new;"> /></span> <span style="font-family:courier new;"> <cc1:modalpopupextender id="modalPopup1" targetcontrolid="scrnShot1" popupcontrolid="panelModal1" backgroundcssclass="modalBackground"></cc1:modalpopupextender></span> <span style="font-family:courier new;"> OkControlID="modalCloseButton1" DropShadow="false" runat="server" />

With just this:

<a href="../Images/Screenshots/desktop1.jpg"><img src="../Images/Screenshots/desktop1_t.jpg" alt="" width="185px" /></a>


Much, much cleaner code!

In summary, I FancyZoom works perfectly in the 3 most likely ASP.Net scenarios. If you need similar functionality in your website then I highly recommend this product!

Tuesday, May 20, 2008

Windows Live Mashups

Here's a neat web page that shows off how developers have mashed up Windows Live maps with other data. Very cool!

Monday, May 19, 2008

Website Mashing: Preliminary Investigation

I've long been fascinated with logically connecting together disparate sets of interconnected data on the Internet. The basic term for doing this is "mashing" or creating a "mash-up". To date most efforts in this regard have been to display various info on a map. HousingMaps.com is perhaps the best known of such sites. It scans Craigslist for rental ads and then overlays these ads onto a Google Map.

I got to wondering how one would do mashing with ASP.Net. My research ending up letting me create two useful methods:

// Get the entire contents of a page
private string GetPageContents(string pageUrl)
{
string pageMarkup = "";
StreamReader streamReader = null;

try
{
HttpWebRequest webRequest;

Uri targetUri = new Uri(pageUrl);
webRequest = (System.Net.HttpWebRequest)HttpWebRequest.Create(targetUri);

// Note : A check was being done here to see if the ContentLength was > 0.
// But some sites mask it with a -1 so we'll skip it.

streamReader = new StreamReader(webRequest.GetResponse().GetResponseStream());
pageMarkup = streamReader.ReadToEnd(); //Response.Write(streamReader.ReadToEnd());
}

catch (Exception ex)
{
Debug.WriteLine("File does not exist - " + ex.Message);
}

finally
{
if (streamReader != null)
streamReader.Close();
}

return pageMarkup;
}


// Find the JPG image links on a web page
private ArrayList GetImageLinks(string pageUrl)
{
ArrayList photos = new ArrayList();

string picPage = GetPageContents(pageUrl).ToLower();
int idx = 0;

do
{
idx = picPage.IndexOf("img src", idx);
if (idx != -1)
{
idx += 9;
int idx2 = picPage.IndexOf(".jpg", idx);
string url = picPage.Substring(idx, idx2 + 4 - idx);
photos.Add(url);
}
} while (idx != -1);

return photos;
}

The first method seems to be quite solid. The second works well for me in my testing but I can't guarantee it will work in all situations.

Sunday, May 18, 2008

Caching Revisited

In my last posting I outlined how I used AJAX Page Methods during the page unload event to explicitly clear the cache objects I had been using. I was very pleased with this accomplishment, for it achieved an important improvement for the app, but also marked a milestone in my learning curve of building more sophisticated web apps.

I did some more reading today though and came across this detailed article about ASP.Net caching techniques. I really focused in on the "Sliding Expiration" option and decided to give it a try. Up until now I was just using caching in its simplest form: Cache[key] = object;

What I learned from the article is that a more sophisticated way to add and/or update objects in the cache is with this method:

Cache.Insert(key, object, dependency, absoluteExpiration, slidingExpiration);


Looking at all the options available, I decided to proceed strictly with Sliding Expiration removal of cache objects. Since I know I'll be using the Cache for future modules, I decided to implement a reusable method in my BusinessObjects DLL as follows:

public static void AddToCache(string key, object data)
{
HttpContext.Current.Cache.Insert(key, data, null, DateTime.MaxValue, TimeSpan.FromMinutes(5));
}

With the Cache.Insert method the first two parameters are obvious. The third, the dependency, I set to null. A more sophisticated technique would be to use it to automatically delete the cache object when the data source changes. But for now I'll leave this for the future.

The fourth parameter is set to "DateTime.MaxValue" as a default of sorts when Sliding Expiration is what you want to implement. Apparently the 4th parameter is ignored if the last parameter is anything but TimeSpan.Zero but I don't know that for a fact. In other words, if you desired an absolute expiration time instead then you would use this syntax:

HttpContext.Current.Cache.Insert(key, data, null, specifyAbsoluteTimeHere, TimeSpan.Zero);

The fifth & last parameter is set to whatever time (in seconds, minutes, etc.) that you wish the cached object to be cleared if not accessed. If it is accessed within that time then the countdown is reset. Why 5 minutes? It's an arbitrary value that I chose but I think one that is appropriate for the application I'm building.

I should mention that back in my webform code page I use a simple, but effective, approach to ensure that each cached object is always available:

if (Cache[key] == null)
Tools.AddToCache(key, GetDataForThisKey());

DataTable dataTable = (DataTable)Cache[key];

Notes
  1. Everything in italics in the code sample is to be replaced as per your particular situation.
  2. "Tools" is a general-purpose class that resides in my BusinessObjects project.
  3. This code example refers to one where the cached object is a DataTable. This is most often the case for my cached objects but clearly can be whatever type of object you want.
In summary, a little tweaking has provided a general-purpose way to use caching effectively, without worrying about the cached objects building up over time!

Saturday, May 17, 2008

Using AJAX Page Methods to Clear Cache Objects Upon Page Unload

I'm nearing the end of my work on this web page editor:

To eliminate repetitive calls to the SQL Server database, I cached a number of data tables including one that is currently over 4,500 records in size. I've read that the ASP.Net Cache apparently does its own housekeeping, removing objects when it needs more space but I thought it good programming practice to explicitly clear all of the objects I cached when the user was done with the editor. But how to do this?

The idea that came to mind was to somehow tap into the local page's "Unload" event. But how? So I posted this on the excellent ASP.Net forums. None of the responses quite gave me the answer but the two fellows responding, one from Maryland and the other from Indonesia (isn't the Internet a great place!), hinted that there must be a way with AJAX.

Doing some more research, it seemed that AJAX Page Methods were the way to go. There's nobody that I know of that has written more about using them with ASP.Net than Dave Ward on his superb Encosia site. So a little more searching found this article.

I had experimented with Page Methods recently and only through Ward's help did I get the example working. But I really didn't completely understand what was going on. The article I just referred to completely opened up my window of understanding about Page Methods! Here's what I've been able to successfully implement:

In the server-side code of the web page I added this:

[WebMethod]
public static void ClearCache()
{
System.Web.Caching.Cache cache = HttpContext.Current.Cache;
if (cache.Count > 0)
{
cache.Remove("CurrMaxIdx");
cache.Remove("OrigMaxIdx");
cache.Remove("Mines");
cache.Remove("Divisions");
cache.Remove("Contracts");
cache.Remove("Levels");
cache.Remove("Muckpiles");
cache.Remove("MuckpileData");
cache.Remove("Activities");
cache.Remove("MajorTasks");
}
}

Then on the web-page itself (the client-side) I added this:

// Clear the ASP.Net Cache objects before leaving the page.
function clearCache()
{
alert('Clear Cache'); // Debugging only
PageMethods.ClearCache(OnSucceeded, OnFailed);
}

function OnSucceeded(result, userContext, methodName)
{
// In this implementation we will do nothing here
}

function OnFailed(error, userContext, methodName)
{
// In this implementation we will do nothing here
}

// Wire in the above Javascript function to the Page Unload event
window.onunload = clearCache;

That's it! It works absolutely beautifully! One interesting tidbit was that initially I added an 'alert' function in the 'OnSucceeded' event but of course it never displayed because the page itself was being unloaded. But I did check that the server-side code was actually running by going back to the page and stepping through the Page_Load code. The cache objects were definitely no more!

Correctly Wiring In a JavaScript Function

I frequently find the unstructured nature of JavaScript to be endlessly frustrating. In some cases you can use several different syntax implementations to do the same thing. Whereas in other cases you have to be deadly accurate.

Here's an example of the latter. I wrote a simple function that starts the process to clear the cache upon a page's unload. What's wrong with this syntax:

function clearCache()
{
// Detailed code here
}

window.onunload = clearCache();


It looked perfectly fine to me. But I kept getting a "Not Implemented" error. Finally, after some searching, I discovered that the last line was incorrect. What it was doing was passing the result of 'clearCache' to 'window.onunload' whereas what I wanted to do was wire-in the JavaScript function itself to this event. So the simple fix was this:

window.onunload = clearCache;

Sunday, May 11, 2008

In Search of the Perfect 3-Column CSS Layout

As any web developer knows, trying to devise a web page layout that has multiple side-by-side columns is not a trivial matter. And then just when you think you've found something that works, it seems to fail when you test it in another browser!

I've done a lot of research on the subject and have come up with something that works pretty good. It's not perfect but it works very well in both IE7 and Firefox 2.0. It's an ASP.Net project that you can download here. Though even if you're not an ASP.Net developer, you should be able to easily pull out the necessary elements to make it work in your development environment.

Credit must be given to Adam McIntyre for the Javascript code that makes sure the columns have an equal height. I modified it a bit but the original idea was his.

Wednesday, May 7, 2008

Web 3.0 Explained ... Simply!

Several years ago I attended a presentation by another software company and the presenter used an acronym that I wasn't entirely familiar with. So I asked him what it meant. He actually didn't know.

Such is my suspicion when people start using esoteric words to describe a concept. About a month ago I received an invitation to the Beta version of what is supposed to be the premier attempt at a Web 3.0 website: Twine This precipitated me to read up extensively on the subject.

My first conclusion is that Web 3.0 is not really that difficult to understand. My second is to try to avoid using the word "semantic" to describe it. Most everyone talking about Web 3.0 uses this "S" word but I doubt that 99% of average people hearing it have no idea what it means! A golden rule of effective communication is to not say anything that the listener/reader does not understand.

In essence, Web 3.0 simply means that the content of every website will be internally stored in such a way as to make it much more searchable, like a giant database. Whereas right now Google and such try to figure out what a page "means" by indexing all the words and phrases on it. But they don't actually understand what the page is about.

For example, imagine two web pages:
  1. The first has these sentences on it: "When I walked into the house I saw several pictures of dogs but never did I meet an actual dog. Only later did I learn that the owner loved dogs but was allergic so couldn't keep any himself."
  2. And the second has this sentence: "When I walked into the house, I was immediately greeted by several dogs."
Now, try to create a search in Google that is equivalent to this sentence: "Find for me the homes of people who own dogs." Both of the aforementioned web pages would be returned. In fact, the first would probably be returned much higher because "dog" is referred to 3 times. But yet there are no dogs in that house.

Web 3.0 tries to interpret each sentence and store it internally in a way that a computer will understand and be able to search.

Monday, May 5, 2008

Manually Wiring Up the AJAX UpdateProgress Control

A common feature of AJAX-enabled pages is to have an animated "Please Wait" image appear while the partial page update is occurring. With ASP.Net this is implemented very easily. Here's an example:

<asp:updateprogress id="UpdateProgress1" runat="server" visible="true" associatedupdatepanelid="UpdatePanel1"></asp:updateprogress></span>
<span > <progresstemplate></progresstemplate></span>
<span > <div class="progress">
<span > <img src="http://blogger.com/Images/Progress/progress_indicator.gif" alt="" /></span>
<span >

</span>
<span > Please Wait</span>
<span > </span></div></span>
<span > </span>
<span > </span>

<span > <asp:updatepanel id="UpdatePanel1" runat="server"></asp:updatepanel></span>
<span > <contenttemplate></contenttemplate></span>
<span > <%-- Content goes here --%></span>
<span > </span>
<span > </span>
</span>

That's all there's supposed to be to it. Refer the UpdateProgress control to the UpdatePanel and it's just supposed to work. My experience hasn't been quite so straightforward. The progress indicator would appear some of the time, but not every time. The reason for this I do not know but my work last week to correctly warn a user when they're leaving a page early taught me about the AJAX Javascript events "Initialize Request" and "End Request". Today I used them to improve the consistency of the Progress Indicator:

<script type="text/javascript">
Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(InitializeRequestHandler);
function InitializeRequestHandler(sender, eventArgs)
{
document.getElementById('<%=UpdateProgress1.ClientID%>').style.display = 'block';
}

Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);
function EndRequestHandler(sender, eventArgs)
{
document.getElementById('<%=UpdateProgress1.ClientID%>').style.display = 'none';
}

</script>


After adding this code, everything now works beautifully. The Progress Indicator displays every time, not just some of the time!

Thursday, May 1, 2008

Warning the User About Prematurely Leaving a Web Page - Update

My earlier posting illustrated how one can easily trap the beforeunload event and decide whether to display a message to the user that exiting the page without first saving the data will result in all unsaved work being lost.

But then I discovered that it was not working properly on my web page. Whenever any of the command buttons - Add, Delete, Move, and Save - were pressed, beforeunload was fired and I would be warned about leaving the page.
This was most confusing because I knew I wasn't leaving the page! But I intuitively knew that there's always a logical explanation so it was just a matter to find it. As I frequently do, I posted a few messages on the ASP.Net Forums. A very bright developer from Portugal named Luis Abreu responded and provided me some sample code. I ran it and his code ran perfectly. So why on earth was mine not working?

On my page I added a simple button, like Luis had done, to force a partial page postback. Pressing it did NOT force the beforeunload event to fire! WTF?!?

I stared at my web page for some time and suddenly realized what the problem was. Those aforementioned command buttons are not sitting directly on the page but instead reside in a Draggable Panel. Also, the Move button causes a Modal Panel to appear. While both of these panels are technically "on the same page", from the perspective of beforeunload I suppose they are not.

So what to do? I thought about it for a second and realized that with each of these buttons I could use the local 'OnClientClick' event to temporarily disable the beforeunload check. With this goal in mind I added this JavaScript function:

function ToggleBeforeUnload(onOff)
{
if (onOff == true)
{
window.onbeforeunload = ConfirmExit; // Activate beforeunload event handler
}
else
{
window.onbeforeunload = null;
}
}


So now, when any of the buttons are pressed, the first thing that occurs is that the beforeunload handler is turned off. But it had to be reactivated. To accomplish this I added a call to this function at the end of each button's server-side method:

private void ToggleBeforeUnload(bool onOff)
{
ScriptManager.RegisterClientScriptBlock(this, typeof(Page), "toggleBeforeUnload", "ToggleBeforeUnload(" + onOff.ToString().ToLower() + ");", true);
}

It calls the same JavaScript function described earlier. And sure enough, it works perfectly!

Incidentally, blessed is the Internet for helping out developers! I distinctly remember the days when there was no Internet. I'm absolutely convinced that it has dramatically improved the productivity and learning curve of developers around the world.

Tuesday, April 29, 2008

Posting HTML Code in a Blog

If you've ever tried posting in a blog then you know that it doesn't work very well. This is because the browser gets confused and tries to interpret the code rather than display it literally.

A simple tool to correct the problem can be found here. It's not perfect in that indentation is lost but other than that it's quite effective.

JavaScript Background Image Swapper

I'm working on a project that involves displaying a world map on one page. I thought it would be neat to display a nighttime map when the user's local time was between 6pm to 6am. I first tried doing this with server-side C# code but it never really worked correctly so I revisited the problem and instead did the whole thing very simply with JavaScript.


Here's the ASP.Net control I was looking to alter:

<asp:Image ID="imageWorldMap" runat="server" Width="100%" Height="100%" />

And here's the JavaScript that gets called when the page loads:

<script type="text/javascript">
// Set the background according to what time of the day it is on the user's computer.
function SetBackground()
{
var now = new Date();
var hr = now.getHours();
var img;

if (hr >= 6 && hr < 18)
{
img = "Images/worldMap.jpg";
}
else
{
img = "Images/worldMapNight.jpg";
}

document.getElementById('<%=imageWorldMap.ClientID%>').src = img;
}
</script>

Monday, April 28, 2008

Warning the User About Prematurely Leaving a Web Page

I'm relatively new to website development. Up until now I've never had occasion to warn the user about leaving a web page. I knew it was possible though, as I'd seen it on some websites before.

I'm now working on a data editor that looks like this:

Essentially, the user can pick one of several bottom-level nodes in the treeview on the left and then the data associated with it will be displayed in the controls on the right. He might just view this data or may alter it. He can also add & delete & move nodes, each of which is connected with a data record.

A novice user wouldn't understand that all changes are handled locally and not updated in the database until 'Save' is pressed. So letting them enter a bunch of data and then close the browser or navigate to another page simply wasn't acceptable.

I found this article by well known ASP.Net guru, Scott Mitchell. It's actually the third in a trilogy of articles on the subject. I followed the basics of what he wrote and added this code to my ASPX page:

var needToConfirmExit = false; // Initialize

function ConfirmExit()
{
needToConfirmExit = Boolean(document.getElementById('<%= needToSaveData.ClientID %>').value);
if (needToConfirmExit)
return "You have made changes that have not yet been saved back to the database. If you leave now, all of those changes will be lost. Are you absolutely sure you want to do this?";
}

window.onbeforeunload = ConfirmExit;


The more advanced part of his article dealt with implementing an extensive client-side mechanism that kept track of which controls had their values changed. As my web page was AJAX enabled, partial page updates were occurring whenever any control's value was altered. So intuitively I knew there must be a simpler way

In the server-side code I already had a property called 'IsDirty' :

public bool IsDirty
{
get
{
return (bool)ViewState["IsDirty"]; // Note: If null then returns as false
}
set
{
ViewState["IsDirty"] = value;
ChangeButtonState(Constants.CommandButtons.Save, value);
needToSaveData.Value = value.ToString().ToLower();
}
}

'needToSaveData' is a hidden field that I placed inside the UpdatePanel. The beauty of this solution is that as soon as a control is updated, the 'ConfirmExit' JavaScript function is ready to go in case the user tries to prematurely leave the web page.

Sunday, April 27, 2008

Modal Popup Extender, StreamWriter

I've made a small, but useful enhancement to the Waikiki condo rental site I built. The owners of the condo have reservations well in advance so only need to find new people a few times a year. As such, they don't want people constantly writing them, saying such things as, "Hey, can I rent it next week?" This is a waste of time for the writer and for the condo owners.

One way to solve this would be for them to keep me updated about when it was available. But that would mean that I'd have to constantly update the site every few months. Sorry, not interested.

So I built a mechanism whereby they can customize a special message that appears when the user clicks on the Contact page. You can view the current message here:

What you're seeing is an effect provided by the ASP.Net AJAX Control Toolkit. It's called the Modal Popup Extender. It lets you display a Panel (ASP.Net's equivalent of a "Div") that contains whatever controls you want in it. In the example, there are only 3 objects:
  1. The panel, which displays a background image
  2. A label, whose text is customized
  3. An "OK" button
The "disabled" background effect behind the popup panel is caused by the extender referring to this simple CSS entry:

.modalBackground
{
background-color:Gray;
filter:alpha(opacity=70); /* For IE */
opacity:0.7; /* For Firefox */
}

There's a new Admin page that I added, that is accessed via a link at the bottom of the page, followed by a required password. I could have implemented a full Login mechanism but thought it overkill for this simple application that will forever be used by only 2 people. With that said, I did not embed the password directly in the code for all code files get compiled into a DLL, which is normally accessible by a hacker via the ASP.net "bin" folder. So instead, I embedded the password in the very secure "web.config" file like this:Arriving on the Admin page, one can only add/edit text and press "Submit" :
Implementing all of this was very simple. For example, the code that writes the text out to a file is this:

if (File.Exists(srcFile))
{
StreamReader streamReader;
string txt;
streamReader = File.OpenText(srcFile);
txt = streamReader.ReadToEnd();
streamReader.Close();

if (txt != "")
textBox1.Text = txt;
}

In my local test environment it worked perfectly. But intuitively I knew it wouldn't work right away on my GoDaddy hosted web server. Why? Three words: File write permissions

The text file in question resides in its own folder. This keeps it separate from the code and markup files. What I had to do was go into the GoDaddy File Manager, select this folder, and alter its Permissions so that Write was enabled and not just Read. Without doing that, the file is forever locked from any changes, short of FTP'ing in a new file.

Just some simple techniques here but together they allow for a nice and useful web application!

Friday, April 25, 2008

An Example of an ASP.Net Website

Earlier this year I was fortunate enough to find a privately owned suite to stay in during my stay in Waikiki. The owners, Irene & Wes, are incredibly nice people and I promised to build them a website to better advertise their suite. It's now done and live.

I must confess that I went well beyond what I had originally intended to do but I'm very pleased with the end result.

On the technical front, I still have a long way to go to be a "CSS Master". I find lining up side-by-side columns especially tricky. I tried to you DIVs as much as possible, rather than tables, but the latter sometimes prove invaluable.

The content of the site is presented in pretty basic ways, but there are two interesting highlights I'd like to draw your attention to:
  1. Maps - These are just embedded Google maps but I think it's incredible what they've done to make it so easy to incorporate sophisticated maps like these into anyone's website. Back in 1995, when the web first dramatically expanded to the general public, did anyone foresee that such information would be available so readily?

  2. Photos + Videos - Photography has been a passion of mine since I was a kid. Now, in the age of digital photography, it's incredible what images one can capture. Beginning with this trip I started using a Canon G9. It's a 12.1 MegaPixel gem that is reportedly "as good as it gets before short of getting an SLR". It's also very good at capturing videos, though I've found that even my 2GB memory card gets quickly filled when doing that!

    Anyhow, in times past, when I wanted to display hundreds of images, I would typically construct a traditional menu of hyperlink buttons like this one. But for this Waikiki site I wanted something different. So I decided to try out the accordion control. I think it worked super well! With so many photos, having companion thumbnails was a must. There is a bit of a delay loading the thumbnails into the cache the first time but it's not insurmountable, at least not with my high-speed connection.
Incidentally, for image manipulation, my current set of tools are these:

Monday, April 21, 2008

"Does not exist in the current context"

The fun & games with Visual Studio continue! I generally love the package but sometimes it frustrates me to no end.

So there I was testing a simple web page, adjusting this & that. Suddenly, when I tried to recompile it I got this message:

labelTagLine does not exist in the current context

This control resides in the header on the Master Page of my project. There's nothing fancy or special about it. I did a comprehensive search and found lots of others who had encountered similar problems with controls suddenly not being recognized. I tried all of their solutions but to no avail.

So eventually I copied main.Master and main.Master.cs outside of the project folder and deleted these two files within the project. Then I created a new, blank version of them. Slowly, I started copying the markup code and C# code back into their respective files, being sure to perform a "Rebuild Website" every so often. Lo and behold, when every last line of code was back, it worked! Something internally must have changed but nothing in my code files did. Another 2 hours wasted. :-(

Melodyne: Sophisticated Music Editing Software

I have zero musical talent but find this an extremely cool piece of software:
You can find out more about Melodyne here.

Debugging with Visual Studio 2005 in Windows Vista

What a Microsoft level headache I encountered this evening. That's my code for, "it should work but it doesn't because of something Microsoft didn't take care of properly".

I wanted to do something similar: Debug a simple ASP.net 2.0 web page in Visual Studio 2005, running on my Windows Vista Ultimate 64-bit machine. I added a Debug.WriteLine statement in the Page_Load event handler of default.aspx.cs and sit it as a breakpoint - as I'd done thousands of times before with Windows XP.

I pressed F5 and looked at the code screen, naturally expecting the app to stop on the designated line. And . . . it didn't! I tried again, but still no go. So I shut down VS2005 and cleared all the DLL files from the bin folder and all the files from the obj folder. I restarted it, recompiled, and tried again. Still didn't stop!

Then I did a ton of research and via Scott Gu's [excellent] blog was taken to this page. There are a LOT of steps. After following the instructions in the last one I tried again but it still didn't work! So then I examined this series of articles by Mike Volodarsky. I followed the VS2005 link and carefully went through the steps needed to get debugging working. I didn't actually go as far as running the app through IIS7 as I intuitively couldn't believe that the internal web server in VS2005 wouldn't debug properly in Vista.

I did some more research and came across this ASP.net forum posting. Note that the second fellow, from Lahore, Pakistan insisted to check the web.config file and be sure that debug is enabled. "Of course it must be", I thought. But when I checked, it was not! So I changed it to:



I hit F5 again and ... it worked!! I don't know for sure whether all those other steps were necessary though I think that installing the hotfix was probably a good idea.

In conclusion, I must say that situations like this are all too common with Microsoft development products. This should have been a "5 minute" thing to fix, but I wasted 90 minutes of my time on it. I simply don't accept that such things should be happening from a paid for product built by a multi-billion dollar company.

Though with the introduction of this blog, I hope to help others save countless time in the future by documenting situations like this and having it picked up shortly by the search engines.