Many products are taking advantage of the enhanced exception management features that the .NET Framework provides, yet very few are going the extra mile to provide instant solutions.
My favorite source code repository is SourceGear's Vault [1]. Besides being written in .NET, it is highly optimized for remote development scenarios. I was installing a new instance of Vault on one of my client's servers the other day, though, and got a cryptic message about a cryptographic exception. I emailed SourceGear's support team, and they responded promptly with instructions on how to fix the issue (even on the weekend).
The details of the fix aren't important. What I found a bit annoying is that the Vault tool itself did not make any suggestions on how to fix the problem. All I got was a stack dump, which I had to email to SourceGear support.
I see this situation all the time. Companies effectively use the .NET Framework's exception management features to prevent their applications from crashing, yet provide only confusing exception messages to the users instead of providing suggestions to fix the problem. When I am building and testing an application, I include instructions in my exception messages to help me fix known problems when I run into them later.
Some of you might be thinking, "That's what a KnowledgeBase is for," and you'd be right. It is equally important to keep accurate documentation of problem/solution scenarios in a KnowledgeBase (SourceGear's did not have my problem listed). Why make your users search through a KnowledgeBase, though, when you can provide solutions directly in the exception management code? Many users don't even bother with the KnowledgeBase and call your support team directly (which can get costly).
You might be asking, "What about issues that I don't know about when I ship my product?" Good question. This is where you need to apply a little bit of ingenuity. I see two possible alternatives to solve the "unknown issue at ship time" problem. For either solution, you first need to implement a mechanism to gather metadata about each exception that occurs in your application, such as exception type, current method, calling method, and so on. You'll need a centralized exception management architecture. If you haven't already built your own, I suggest that you take a look at the Microsoft Exception Management Application Block [2].
The first way that you could effectively utilize the exception metadata that you have gathered is to borrow a page from the anti-virus software market and build a KnowlegeBase definition update process into your application that would keep the suggested solutions in your application fresh.
If you don't like the thought of having to download KnowledgeBase data into your application, a second alternative is to use your exception metadata to build a hyperlink to your company's KnowlegeBase Web site. That way your user can click on the hyperlink to check if their exception has a solution, rather than sift through the KnowlegeBase themselves. Of course, you'd have to allow the user to copy the hyperlink to the clipboard in case the machine with your application on it doesn't have access to the Internet.
Your goal should be to make your application as issue-free as possible, but your secondary goal should be to make life for your users as easy as possible, if/when they run into an exception. If you are building an application, then either of my suggestions would work well. If you are a component vendor, then my second suggestion is much more practical. Give it a try, and you'll be surprised at how much your support efforts and costs go down.
Do Not Forsake Thy DataReader
I love the speed and efficiency of DataReaders (implemented as the provider-specific OleDbDataReader, SqlDataReader, etc.). Their primary function as a forward-only firehose cursor, though, causes its biggest problem. What if you need to use the data in it twice? For instance, suppose you are building an ASP.NET page that has a billing address and shipping address on it. Naturally, both of these page sections will have a DropDownList of states. It would be a waste to query the database twice to fill both DropDownLists. It would be equally wastefull to fill up a bloated DataSet or DataTable with such simplistic information.
There is a better solution, and it takes very little code to implement:
Dim dr as SqlDataReader
...code to populate dr...
With ListBox1
.DataSource = dr
.DataTextField = "StateCode"
.DataValueField = "StateName"
.DataBind()
End With
With ListBox2
.DataSource = ListBox1.Items
.DataTextField = "Text"
.DataValueField = "Value"
.DataBind()
End With
The key to understanding the code above is knowing a little bit about how the DataSource property of ASP.NET Web controls works. The DataSource property can accept any object that implements the IEnumerable interface. Most of the time that object is an ADO.NET object, but it doesn't have to be. In the case of the example above, the Items property of the DropDownList control implements the IEnumerable interface, so it is possible to bind the second DropDownList of states to the Items property of the first one.
Another key point to note is that you are binding to a ListItemCollection object, which contains an internal collection of ListItem objects. Therefore, you can bind to any property implemented by the ListItem class. Obviously, the best approach here is to translate the Key and Value properties directly from the first DropDownList, but there is no rule that says that it has to be that way. In fact, if you leave off the data binding instructions, the Text property of each ListItem in the first DropDownList will be used for both the Key and Value properties of the second DropDownList. Keep in mind that the second ListBox has its own distinct set of ListItems, such that if you make additions, changes, or deletions to the items in ListBox1, they will have no affect on the items in ListBox2.
So, as you can see, you don't have to give up on using the optimized DataReader classes just because you need to use their data more than once, and you don't have to populate an intermediary collection object to hold the results. A little ingenuity provides an effective solution.
POST It Notes
Since .NET supports SOAP-based Web services out of the box so well, many people are lulled into thinking that this is the only way that they are used. Not true. There are a few toolkits out there to help you call .NET Web services from a non-.NET platform (such as classic ASP) using SOAP, but it's often far easier to just use HTTP-GET or HTTP-POST. For that, I use the XMLHTTPRequest object in the Microsoft XML class library.
I ran into an interesting exception the other day after I deployed a .NET Web service that had worked locally on my testing server. When I attempted to access it using the XMLHTTPRequest object from a VB6 application (yes, some of us still use it occasionally), I got an "invalid protocol" exception. I spent a long time debugging my Web service before I figured out that by default, ASP.NET v1.1 disables remote access to Web services via HTTP-GET and HTTP-POST.
Tucked away in your machine.config file is the following section:
<system.web>
...other config info
<webServices>
<protocols>
<add name="HttpSoap1.2"/>
<add name="HttpSoap"/>
<!-- <add name="HttpPost"/> -->
<!-- <add name="HttpGet"/> -->
<add name="HttpPostLocalhost"/>
<add name="Documentation"/>
</protocols>
...other config info...
</webServices>
</system.web>
Note how the HttpPost and HttpGet tags are commented out. Also note how there is a tag named HttpPostLocalhost that allows access to Web services via HTTP-POST when you are testing locally (which is why the problem didn't crop up until I deployed my Web service to my testing server).
Once I discovered this section of the machine.config file, the problem was easy to solve. I simply uncommented the HttpPost tag. What really threw me at first was that I had deployed this architecture successfully before, without having to do this step. Then, I remembered that my last solution used ASP.NET v1.0, and sure enough, remote access to Web services via HTTP-GET and HTTP-POST are allowed by default in v1.0. I'm sure that the change in defaults is a case of Microsoft becoming more diligent about security and leaving things turned off unless specifically turned on, but I really do wish that they had included a more intuitive error message that would have saved me a couple hours of frustration. (Are you sensing a pattern here?)
LINKS:
[1] http://www.sourcegear.com/vault/
[2] http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/emab-rm.asp