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.