Throwing the wrong exception

The .NET WebClient class abstracts away most of the complexity associated with downloading data from and uploading data to Web sites. Once you instantiate a WebClient instance, you can upload or download a page with a single line of code. For example:

var MyClient = new WebClient();
// Download a page
string pageText = MyClient.DownloadData("http://example.com/index.html");
// send a file via FTP
MyClient.UploadFile("http://example.com/uploads/file.txt", "file.txt");

WebClient also has methods for easily doing asynchronous requests so that you can write, for example:

MyClient.UploadFileCompleted += UploadCompletedHandler;
MyClient.UploadFileAsync(new Uri("ftp://example.com/file.txt"),
     "STOR", "file.txt", null);

The file will be uploaded in the background and when it’s finished the UploadCompletedHandler method is called.

As always, the devil is in the details. Documentation for UploadFileAsync says that the method will throw WebException if the arguments are incorrect or if there are errors downloading. For example, if the fileName argument is empty, the method is supposed to throw WebException.

Unfortunately, it doesn’t. This line should throw WebException:

MyClient.UploadFileAsync(new Uri("ftp://example.com/file.txt"), "STOR", string.Empty, null);

Instead, it throws InvalidCastException in a rather odd place, as you can see from this stack trace.

System.InvalidCastException: Unable to cast object of type 'System.ComponentModel.AsyncOperation' to type 'UploadBitsState'.
 at System.Net.WebClient.UploadFileAsyncWriteCallback(Byte[] returnBytes,
 Exception exception, Object state)
 at System.Net.WebClient.UploadFileAsync(Uri address, String method, String fileName, Object userToken)
 at testo.Program.DoIt() in C:\DevWork\testo\Program.cs:line 38

This is a problem because WebClient.UploadFileAsync doesn’t say anything about throwing InvalidCastException. If you’re writing code that handles possible errors, you’re going to allow for WebException, but you’ll ignore InvalidCastException. At least, that’s how you should write the code: only catch those exceptions you know about and are prepared to handle. InvalidCastException thrown by UploadFileAsync is definitely unexpected and is an indication of a more serious error.

Ever curious, and since I already had the .NET Reference Source installed, I thought I’d go see why this happens.

The line that throws the error is in a method called UploadFileAsyncWriteCallback, the first few lines of which read:

 private void UploadFileAsyncWriteCallback(byte[] returnBytes, Exception exception, Object state)
{
    UploadBitsState uploadState = (UploadBitsState)state;

So the value passed in the state parameter must be of type UploadBitsState or of a type that can be cast to UploadBitsState. Otherwise the cast is going to fail. Something, it seems, is passing the wrong type of value to this method.

The code that makes the call is in the UploadFileAsync method. The code in question looks like this:

try
{ 
    // Code that uploads the file.
}
catch (Exception e)
{
    if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException)
    {
        throw;
    }
    if(fs != null)
    {
        fs.Close();
    }
    if (!(e is WebException || e is SecurityException))
    {
        e = new WebException(SR.GetString(SR.net_webclient), e);
    }
    // This next line causes the error.
    UploadFileAsyncWriteCallback(null, e, asyncOp); }

I took out the actual uploading code, because it’s not really relevant here. The problem is in the exception handler, in particular the last line. The asyncOp that is being passed as the last parameter to UploadFileAsyncWriteCallback is of type AsyncOperation. But we’ve already seen above that UploadFileAsyncWriteCallback expects the last parameter to be UploadBitsState. Oops.

Because this bugs is inside the exception handler, any exception (other than ThreadAbortExceptionStackOverflowException, or OutOfMemoryException) that is thrown in the code will end up causing UploadFileAsync to throw InvalidCastException.

Until the code is fixed, there’s nothing you can do to avoid this bug other than by not calling UploadFileAsync. You can code around it by writing your code to handle InvalidCastException in the same way that it would handle WebException, but understand that doing so will hide other problems. If, for example, something caused UploadFileAsync to throw SecurityException, it’s going to be reported as InvalidCastException, and your code won’t be able to tell the difference. It’s not like there’s an inner exception to look at.

I’ve reported the bug at Microsoft Connect. The number is 675575, WebClient.UploadFileAsync throws InvalidCastException.

Update 2011/07/30

The following is from an email acknowledgement I received from Microsoft Connect:

This appears to be a regression in .NET 4.0 and we will plan to address it in a future release. Note that all of the Async Upload methods are similarly affected.