Simple Asynchronous Downloads

There are probably as many strategies for downloading files from the Internet as there are .NET programmers -- that is, no where near the number of Classic VB developers, but still more than a mere handful. Ask the question in a newsgroup, and you'll quickly get a good half-dozen suggestions and a handful of links, all pointing to different ideas. But rarely, if ever, do I see anyone suggest what I consider to be probably the single most powerful "unknown" native method Classic VB offers: the AsyncRead method of UserControl and UserDocument objects.

Two things I like best about AsyncRead is that it's native -- no external dependencies are dragged into a project -- and it's asynchronous. When you call AsyncRead, execution returns immediately to your application, and one or more events are later fired alerting you to the download status. VB5 introduced the native UserControl and VB6 improved on it a bit -- in particular, in this very area. Both versions will fire an AsyncReadComplete event when the entire file has been received. VB6 also fires AsyncReadProgress events at semi-random intervals during the download process.

While you can certainly use these techniques in VB5, you'll find them somewhat more flexible in VB6. I'll point out the areas with significant improvements. You can use conditional compilation to easily take the most advantage of the newer capabilities VB6 offers. By defining a conditional constant in your UserControl that tells it whether it's being compiled into a VB5 or VB6 app, you selectively enable the enhanced capabilities of VB6 when available. For example, here's a simple method that kicks off a new download:

Microsoft actually provides a rudimentary Knowledge Base article on using AsyncRead, so I'll just focus on some of the more interesting aspects. Note that VB6 offers an array of option flags (AsyncReadConstants) that control how downloads are handled with respect to the cache. This allows you to force a new download (essentially, a Refresh), just use whatever's already cached (Offline-mode), only update files if the cached copy is older and so on. I've offered this as an optional parameter, for VB6 builds, in the routine above.

Probably the most important aspect to be aware of is the PropertyName parameter, to which I'm here passing a fairly unique string (GetTickCount). AsyncRead actually supports multiple simultaneous downloads, and this property is the only way you can control specific downloads. In this case, I chose to write a very simple control that just supports a single download, and cancels pending downloads using the CancelAsyncRead method when a new one is requested:

Note that I also requested the data be delivered in a Byte array when I made the AsyncRead call. This seems more flexible than asking for a file to be created, as that's one of the simplest of all MSBASIC operations. You might want to play around with requesting a Picture object, though, if you're downloading images. But converting Byte arrays to Picture objects isn't very difficult; see the NetCam sample on my site for the code to do that.

When AsyncRead completes a download, an AsyncReadComplete event (d'oh!) fires. This is the spot to package up the received bytes, perhaps stashing them in a module level array for the user to grab as needed, and raise an event to notify the user:

Providing the raw data to your user is as simple as exposing a read-only Bytes property:

#If VB6 Then
Public Property Get Bytes() As Byte()
#Else
Public Property Get Bytes() As Variant
#End If
' NOTE: Change conditional constant at top
' of module to match target language!
Bytes = m_Bytes()
End Property

What could be simpler? You can download a completely functional copy of the NetGrab UserControl I wrote, along with a little demo showing how to use it, from my site.

Self-Updating Applications
When I showed a friend my little NetGrab control, his first reaction was that it'd be just perfect for providing an automated background download of program updates. Which, indeed, it would. You could easily use this control, together with a timer or just at startup, to occasionally check whether new versions are available. (Of course, don't do this without your user's permission!) If you don't want to burden the system with Timer ticks even once per minute, download the TimerObj sample from my site for a code-based timer that works with Interval settings in the Long range.

The best approach for a self-updating application actually involves bringing another little utility app into the equation. The perils of actually over-writing the executing application are just too great. So the simplest solution is to either write an app-launcher that first checks for available updates, downloads and installs them if available, and then launches the main application. A slight twist on this would be to download the update in the background, then ask the user if they'd like to restart, at which point you'd spawn your over-writing launch helper.

About the Author

Karl E. Peterson wrote Q&A, Programming Techniques, and various other columns
for VBPJ and VSM from 1995 onward, until Classic VB columns were dropped entirely
in favor of other languages. Similarly, Karl was a Microsoft BASIC MVP from 1994
through 2005, until such community contributions were no longer deemed valuable.
He is the author of VisualStudioMagazine.com's new Classic
VB Corner column. You can contact him through his Web
site if you'd like to suggest future topics for this column.