Staying On The Correct Path

In a current task where I had to consolidate the structure of a website and organize the files better, I ran across this interesting quirk.  When developing, we work in a virtual directory on our local machine, while on a real server, it runs in the root directory.  This poses problems whenever we want to use relative paths to images, style sheets, etc.

The key to fixing this is the VirtualPathUtility, which has the logic in it to determine the root of the web application and return a proper path.  I thought it would be easy to just use this object right in the markup like:

<head runat="server">
    <title></title>
    <link id="cssLinkOutside" href='<%=System.Web.VirtualPathUtility.ToAbsolute("~/StyleSheet.css")%>' rel="stylesheet" type="text/css" />
</head>

That didn’t work.  The rendered HTML turned out to be:

<link id="cssLinkOutside" href="&lt;%=System.Web.VirtualPathUtility.ToAbsolute(&quot;~/StyleSheet.css&quot;)%>" rel="stylesheet" type="text/css" />

Pretty literal.  The cause of this odd behavior is that the HEAD tag is set to runat=”server”.  If that piece is taken out, the embedded code works well.  So if you need the HEAD to be a server control – like when using themes – then what?  You can add code in the code-behind to do an attributes.add() on the LINK tag.  But having to add code in two places for such a simple need is just too much, especially when I was looking at updating a couple dozen pages.

The workaround is kind of surprising: Wrap the tag that contains dynamic code in a placeholder.

<head runat="server">
    <title></title>
    <asp:PlaceHolder runat="server" id="holder">
        <link id="cssLinkInside" href='<%=System.Web.VirtualPathUtility.ToAbsolute("~/StyleSheet.css")%>' rel="stylesheet" type="text/css" />
    </asp:PlaceHolder>
</head>

This renders the tag properly:

<link id="cssLinkInside" href='/WebTestbed/StyleSheet.css' rel="stylesheet" type="text/css" />

Now This is Something Worth Looking Into

When you see something like this in the call stack, you have to know more:

[InvalidOperationException: This SqlTransaction has completed; it is no longer usable.]
   System.Data.SqlClient.SqlTransaction.ZombieCheck() +1623536
   System.Data.SqlClient.SqlTransaction.Rollback() +172
   Framework.Common.Database.RollbackTransaction() in C:\Framework\Common\Database.vb:413 
   Test.uxCreate_Click(Object sender, EventArgs e) in C:\Test.aspx.vb:47
   System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +154
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3691

And the error message is interesting as well: “This SqlTransaction has completed; it is no longer usable”.

A little background on what is going on: this is a database class that is managing a SQL transaction to be used by multiple objects.  The database object instance gets passed along to the different objects and they use it, participating in the transaction.  Somewhere along the way, maybe a commit is occurring and then the transaction becomes invalid.  That’s how it seems.

Tossing in a bunch of debug.writelines to see what what happening inside the methods popped up a message about a SQLException being raised because of a non-existent column name.  Fixing the schema problem fixed the zombie problem.  But what was the reason for the original error?

Let’s say you bring a sandwich into work.  You give that sandwich to someone and tell them to put it in the refrigerator for you.  This person (the fridgemaster) puts it in the fridge and comes back to the refrigerator a little later to find your sandwich moldy and spoiled, so he throws the sandwich out.  Later on yet, you go to the fridgemaster and ask for your sandwich.  He says “this sandwich has spoiled; it is no longer usable.”  In response to your puzzled look, the fridgemaster says “I did a ZombieCheck and it was moldy.”

In coding terms, the order of events was: TX started, SQL error occurred, TX rolled back (by SQL Server), SQLException raised and caught, TX rolled back in catch block (by user code) and unable to because the TX already was rolled back by SQL Server.  This rollback behavior is dependent on the severity of the error. In the case of the schema error, which presumably was interpreted as “this will NEVER work”, this equated to being severe enough to roll back the transaction.

Too Many Items In Combo Box: When One Is Just One Too Many

I got to troubleshoot a dumb error message today.  The error was "Too many items in combo box."  The situation was anything but.  I was only adding one item.

So I got it working and I wanted to find out why it happened in the first place.  The error it should have returned was "Value cannot be NULL" because that was the root of the problem.  So here’s a distilled piece of code to illustrate the problem.  Create a form and put a combo box on it.  The code for the form should look like:

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ 
        Handles MyBase.Load

        Dim d As New DisplayItem
        ComboBox1.Items.Add(d)

    End Sub

End Class

Public Class DisplayItem
    Public Name As String

    Public Overrides Function ToString() As String
        Return Name
    End Function

End Class

The problem is the combo box is trying to display DisplayItem.Name, because that is what the ToString says to do, but the value of Name is Nothing.  You can fix this by setting the value of Name to String.Empty or something else.  The odd thing is that you can also fix the problem by commenting out the ToString override.  To figure this out, I fired up Reflector and went to see what was going on behind the scenes.

This particular situation is basically bypassing all the safe value checks done when adding an item to a list control.  I suppose Microsoft should add a test case for this scenario, but really, if the programmer is attentive, this shouldn’t happen.  I, naturally, happen to be inattentive.

Behind the scenes of the Add method of the Items collection, the first check is in the AddInternal method.  Since we’re passing in an instance of DisplayItem, it passes that check.  The next step is in the NativeAdd method.  At this point, we’ve done our NULL checks and it is assumed we can convert the object to a string.  This method now calls GetItemText.

GetItemText parses the properties of the object passed in and gets the string value.  If the DisplayMember property is not set, the control uses the ToString value of the object itself.  Because we overrode ToString, the control trusts us and returns the value from ToString, the Name value.  This turns out to be Nothing – Oops!  We’ve already passed the check for Nothing, so this sends bad data to the Win32 API, bubbling a failure error code back to NativeAdd.  If NativeAdd gets anything but a success, it always returns the message “Too Many Items In Combo Box”.  But the real reason is that you snuck a Nothing past the initial validation.

Interestingly, if the DisplayMember is set, and the value of the property is Nothing, it is handled properly in GetItemText.  If we converted Name to a private field and made a public property, then set the DisplayMember of  ComboBox1, it would work.  If your display member is another object that overrides the ToString function, you can get around that check as well and return Nothing, causing a failure.

The simple solution for this error message is to avoid NULL values.  The bottom line is to have .ToString always return a string, never Nothing.