E-Commerce Checkout Usability Issues

I develop e-commerce applications for a living and I love what I do. The best part about my job is that it gives me the chance to continue to develop software, but also be involved in actual business decisions that yield real results. During the busy holiday season leading up to Christmas mean that e-commerce web sites are some of the busiest sites on the Internet. This means that e-businesses are relying on the month between Thanksgiving and Christmas to really add to their yearly bottom line. Since its the time of year where thousands of people hit the Internet to do their Christmas shopping, and I’m no different, I thought I’d bring up an issue I ran into while doing some of my online Christmas shopping. I’m going to keep my examples really vague until after Christmas in case my wife or other family members read my blog.

The handful of websites (high profile websites actually) I visited were all fairly well designed and responded rather quickly. That’s key during a busy e-tail season. I ran into issues on most of them during checkout. One site asked me to register during checkout, which I typically do, but took me back to my cart after registration instead of letting me continue my purchase. I was sort of confused because the cart details at the top of the page were really small, so I didn’t really know what to do next. If people expect to make their purchase when they click checkout, let them continue to do so. Don’t hinder the process.

Another website I visited, like most out there, allowed me to enter an offer code during checkout. Much to my delight, they let you stack coupons. So I scoured the Internet for some coupons and entered them into their offer code page during checkout. Much to my chagrin, none of them went through. However, I didn’t get a notification that the coupon was invalid. It just removed it from the offer code section and didn’t tell me anything about what might be wrong. In the end, I couldn’t use ANY of the offers I had and actually make it through checkout. All it would have taken was to tell me the offer code was invalid. What would have been better would have been to process them all at once and tell me which codes were valid and which weren’t.

I find its really important to keep your e-commerce website as usable as possible. The checkout process is the most important part of the site and you want people in and out as quickly as possible so you can capture that conversion. If you make things difficult or don’t provide usable feedback, you may lose a customer. That can have a real impact on your bottom line.

C# Inferring SqlDbType via Reflection

Today, I was looking for a way to take a string representation of a SqlDbType, i.e. System.Data.SqlDbType.VarChar and actually return the enumeration value (System.Data.SqlDbType is an enumeration of valid SQL data types like VarChar, Char, Int, etc.). Of course I could have done something like:

public static SqlDbType GetSqlDbType(string sqlDbType)
{
    switch (sqlDbType)
    {
	case "varchar":
		return SqlDbType.VarChar;
	case "char":
		return SqlDbType.Char;
	case "int":
		return SqlDbType.Int;	
	case "bigint":
		return SqlDbType.BigInt;
	default:
		throw new Exception(String.Format("{0} is either an unknown or unsupported SqlDbType.", sqlDbType));
    }
}

The drawback to this solution is that I would need a giant switch statement for all of the possible SqlDbType possibilities. Plus, what if for some magical reason, a new one is added? So I turned to Google to help me find a more elegant example, and I found what I was looking for:

SqlDbType sdt = (SqlDbType)Enum.Parse(typeof(SqlDbType), "VarChar", true);

This allows you to find the SqlDbType value you are looking for by using reflection on the SqlDbType enumeration. Nice! Of course, I wanted a more complete solution for my needs, so I came up with something like this (based on the first example):

public static SqlDbType GetSqlDbType(string sqlDbType)
{
    if (!String.IsNullOrEmpty(sqlDbType))
    {
	try
	{
	    string hintText = sqlDbType.Substring(sqlDbType.LastIndexOf(".") + 1);

	    SqlDbType sdt = (SqlDbType)Enum.Parse(typeof(SqlDbType), hintText, true);
	    return sdt;
	}
	catch (ArgumentException)
	{
	    throw new Exception(String.Format("{0} is an unknown SqlDbType.", sqlDbType));
	}
	catch (Exception e)
	{
	    throw e;
	}
    }

    throw new Exception("Provided value for SqlDbType was null or empty.");
}

This lets us do a couple of important things. First, it allows for support of the complete namespace for a SqlDbType, i.e. System.Data.SqlDbType.VarChar AND just VarChar. Second, it lets us catch important errors like an unknown SqlDbType value that is passed in to GetSqlDbType.

That’s it! Enjoy! If anyone has any questions or comments, you know what to do…

Manage MacBook Pro Battery with Binary Tricks’ Watts

My good buddy Kyle pointed me in the direction of a great little app for OS X to manage your MacBook Pro battery. Its from Binary Tricks and its called Watts. This little app, which only costs $6.95 by the way, allows you to calibrate your battery through 5 easy steps. Various people out there on the intertubes suggest you calibrate your battery once a month to maximize its life. I’m on my second 17″ MacBook Pro battery after the first one suffered the infamous “Swollen Battery” problem almost 2 years ago. That said, I want to get as much out of this one before ponying up another $130 for a new battery. I’ve been using Watts now for about a month and so far I love it and highly recommend buying it.

Speaking of new batteries, Other World Computing offers new 17″ MacBook Pro batteries from NuPower for only $99.99. They supposedly hold a longer charge and last much longer than those sold directly by Apple. I haven’t tried one yet though. They also sell Battery Charger/Conditioner devices for them. I’m not sure I’d pony up the cash for one of those though.

Invalidating Content on Amazon CloudFront with ASP.NET SDK

I’m working on integrating some of Amazon’s Web Services into out eCommerce platform. I’ve been working on performance enhancements on and off for the last year and content delivery is the last big step for us. Getting started on S3 and CloudFront was pretty easy, but I ran into some issues when updating content in our S3 buckets. Luckily, Amazon added the ability to do this at the end of August. Since we use ASP.NET, I’ve started to work with their .NET SDK. Turns out, its pretty easy to invalidate some content.

public bool InvalidateContent(string distributionId, List<string> files)
{
    AmazonCloudFrontClient client = new AmazonCloudFrontClient(Settings.AWSAccessKey, Settings.AWSSecretKey);

    PostInvalidationRequest request = new PostInvalidationRequest();
    InvalidationBatch batch = new InvalidationBatch();

    batch.Paths = files;
    batch.CallerReference = GetHttpDate();

    request.InvalidationBatch = batch;
    request.DistributionId = distributionId;

    PostInvalidationResponse response = client.PostInvalidation(request);
    if (!String.IsNullOrEmpty(response.RequestId))
    {
	bool bInProgress = true;
	while (bInProgress)
	{
	    GetInvalidationRequest getReq = new GetInvalidationRequest();
	    getReq.DistributionId = distributionId;
	    getReq.InvalidationId = response.Id;

	    GetInvalidationResponse getRes = client.GetInvalidation(getReq);
	    bInProgress = getRes.Status.Equals("InProgress");

	    if (bInProgress)
	    {
		Thread.Sleep(Settings.AmazonWaitTime);
	    }
	}
    }
    else
    {
	return false;
    }

    return true;
}

InvalidateContent expects a CloudFront Distribution ID and a list of S3 files to invalidate. References to the static Settings class is just a class that reads in configuration settings from a configuration file, be it App.config or any configuration file you wish to set up. Basic values to store are your AWS Access Key, AWS Settings Key, and a wait time value before requesting information from Amazon again. There is also a method GetHttpDate(), which just returns:

System.DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss ", System.Globalization.CultureInfo.InvariantCulture) + "GMT";

InvalidateContent() will make a PostInvalidationRequest (which is part of the ASP.NET AWS SDK) through an AmazonCloudFrontClient object. If this request is successfully posted, Amazon will return you a RequestId value that you can use to poll for the completion of your request. Keep in mind that you can only invalidate 1,000 S3 objects a month. After that Amazon will start to charge you $0.005 per object invalidated. This is per file, not per invalidation request. Hopefully you found this helpful!

Unable to Sell Your Electronics? Don’t Pay to Recycle!

I had these 2 old CRT televisions sitting in my condo for the last 4 years. One my wife and I never watched. The other we watched only when we were being lazy on a Sunday morning or one of us was sick in bed. We decided to try not having a TV in the bedroom, so I went to Craigslist to sell them both. Over 2 months, I probably posted my ads 4-5 times lowering the price of the televisions each time. I think by the last try, I was asking $25 for a 27″ and $15 for a 19″. Still no takers.

I figure the cost of paying to recycle these suckers (they are super heavy!) would have been somewhere between $50 and $100. There was no way I was just going to find a random dumpster and toss them. That’s in no way environmentally responsible. So, I stuck them out at the street in front of my workplace around 12:45 and put a sign on them that said “FREE (works just fine)”. I figured they might be gone by the end of the day if I was lucky. For giggles, I went out around an hour later and they were both gone! YES! Sending stuff off to a new home for free gave me the ability to do the right thing without having to pay someone to do it. Now I have free space in my condo and the good feeling of having done what I thought was the right thing.

So, if you have electronics in your house that you can’t sell or can’t give away online, put it out in front of a busy street near where you live or work (or even at a friend’s place) and put a FREE sign on them. Bet you your electronics find a happy new home!

GMail Usability Issue – Spam/Delete Buttons

I work on web applications every day and usability is a huge issue, mostly because you’re dealing with such a diverse set of users. The same goes for any web application out there. Something about GMail has been nagging me for a while now, and I just lost my proverbial #$@! over it this afternoon. Whoever over at Google decided that it was a good idea to put the “Report Spam” button exactly to the right of the “Archive” button? I’m guessing the same person that though putting “Delete” next to “Report Spam”. The issue I have here is that when I’m on cruise control working on my computer, I sometimes inadvertently click the Report Spam button when trying to Archive a message. Yeah, I should probably slow down a bit and it wouldn’t happen, but it does. So my biggest question to Google is that are these buttons needed right next to each other? Honestly there is no relationship between “Archive”, “Report Spam”, and “Delete”. They do completely different things and if you don’t catch a mistake, you might lose messages forever. My suggestion to Google is this; put “Archive” and “Report Spam” all the way to the right of the menu bar.

My general rule of thinking here is that “Archive”, “Labels”, “Actions” all mean you want to keep a message and move it somewhere else. “Delete” and “Report Spam” are get this message outta here forever types of actions. So, Google, switch this up so we don’t accidentally screw up our Inbox. Please? Pretty Please? With sugar on top?

ASP.NET SQLCacheDependency with SQL Query Notifications

I’m going to make this quick and dirty. I’ve set up my ASP.NET web applications to leverage SQLCacheDependency with SQL Server 2005 Query Notifications. The setup process takes a some time and debugging can be tricky. But the payoff can be enormous. The bottom line is that the performance increase on page load times is well worth the effort, especially for data that doesn’t change all that often. I found it really useful for caching product data on my eCommerce sites, especially as the number of products in the system grew to over 5,000. However, I always seem to miss a step when when configuring my SQL Server 2005 databases; so this post is for my own reference, but if it helps someone else out there, even better.

The original article I used as a basis for configuring my applications is at the url below:

http://www.simple-talk.com/sql/t-sql-programming/using-and-monitoring-sql-2005-query-notification/

Follow the steps there and you’re good to go. Especially use the SQL Profiler debug steps at the bottom of the article if you get tripped up. One thing that I always had to do with my databases to get everything to work properly was execute the following query:

use <dbname>
EXEC dbo.sp_changedbowner @loginame = N'<dbuser>', @map = false

Make sure you use this caching technique responsible though. The Query Notifications can use up processing power so only cache data that you know will give your application a performance bump. Also beware of memory usage as you cache more and more data. You could end up caching so much data your application needs to restart often and that could cause slow page load times.

Easily Cause StackOverflow Exception in ASP.NET Web Application

This is a short one, but since I can’t believe I’ve never managed to do this before, I thought I’d post a little tidbit on it. I threw a StackOverflow in one of my ASP.NET C# web applications today. Watching the request keep going for about 5 minutes, I checked the Event Log on the server. There it was, StackOverflow exception. Say, what?

How did I manage it? Recursively add a control to its own control collection. The .NET framework just freaks and the application pool just gets restarted after each exception kills the app. Glad it didn’t take long to figure it out!

Apple Tablet Coming Soon?

Rumors that Apple was going to release a Tablet computer have been running abound for years. However, it appears that the Apple Tablet is going to become reality next week, Jan. 27th, 2010. People in the know think it will resemble an iPhone, albeit a lot larger. It won’t run a typical operating system like other PC tablets out there. So, what do I think of this? To be honest, I’m not all that excited.

I have an iPhone and I love it. I don’t know how I lived without it. Combined with my MacBook Pro, they’re the two most useful tech tools I have in my arsenal (other than my brain I guess). But I just don’t see how I’d use an Apple Tablet computer. It just doesn’t seem all that useful of a tool to me. Maybe I just don’t know enough about it. What would I need a “larger iPhone” for?

I’m actually a little afraid that this could be another “Mac Cube” miss-step for the folks over at Apple.

Properties In a Active Directory Computer Object

I’m working on a project and I was curious about what properties Active Directory would return when search for computers with the DirectorySearcher class in C#. Basically, you can search an entire domain for known computers using something like:

string domainName = "mydomain";
DirectoryEntry de = new DirectoryEntry();
de.Path = String.Format("LDAP://{0}", domainName);
de.Username = "username";
de.Password = "password";

try
{
     DirectorySearcher ds = new DirectorySearcher();
     ds.Filter = "(&ObjectCategory=computer)";
     SearchResultCollection results = ds.FindAll();

     foreach (SearchResult sr in results)
     {
             ResultPropertyCollection myResultPropColl;
             myResultPropColl = sr.Properties;
             foreach (string myKey in myResultPropColl.PropertyNames)
             {
                 string propString = String.Empty;
                 string tab = "    ";                                
                 Console.WriteLine("{0} = ", myKey);
                 foreach (Object myCollection in myResultPropColl[myKey])
                 {
                     Console.WriteLine("{0}{1}", tab, myCollection);
                 }
         }
     }
}
catch (Exception)
{
}

This code is basically borrowed from this MSDN article, but what I was originally after was what properties could I actually get at? I wasn’t all that interested in the code. Well, just running that code yielded me the following list of properties that you can access via a computer object from Active Directory:

operatingsystem
countrycode
cn
lastlogoff
dscorepropagationdata
usncreated
objectguid
iscriticalsystemobject
serviceprincipalname
whenchanged
localpolicyflags
accountexpires
primarygroupid
badpwdcount
objectclass
instancetype
objectcategory
whencreated
lastlogon
useraccountcontrol
samaccountname
operatingsystemversion
samaccounttype
adspath
serverreferencebl
dnshostname
pwdlastset
ridsetreferences
logoncount
codepage
name
usnchanged
badpasswordtime
objectsid
distinguishedname

For my purposes, I wanted to find out operating system information, so the operatingsystem and operatingsystemversion properties woulded nicely for me. But, there you go, all of the properties you can access from a computer object in Active Directory. I hope this is useful to someone else out there other than myself.