0

I'm writing an application that manages a library of mods/addons for a game. Every so often, one of these mods has an update available, and the new version is downloaded using WebClient.DownloadFileAsync(), having retrieved the filename through reading the Content-Disposition in a WebRequest response.

When two or more updates are available, the first downloads perfectly fine but when you try to download a second file without having restarted the application WebClient freezes, the file is created with the retrieved name, but contains 0 bytes and neither the DownloadProgressChanged or DownloadFileCompleted events are triggered.

If I do not try to retrieve the original filename, and just give them a name locally, then WebClient doesn't freeze. But I need the original filename.

Is there anything I can do to avoid this issue, while still being able to retrieve the original filename?

private void Download(string url, string downloadDirectory)
{
    WebClient wc = new WebClient();
    string filename = FilenameFromURL(url);

    wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
    wc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadComplete);

    wc.DownloadFileAsync(new Uri(url), downloadDirectory + filename);
}

private string FilenameFromURL(string url)
{
    return new ContentDisposition(WebRequest.Create(url).GetResponse().Headers["Content-Disposition"].ToString()).FileName;
}

private void DownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
    downloadProgressBar.Value = e.ProgressPercentage;
}

private void DownloadComplete(object sender, AsyncCompletedEventArgs e)
{
    // ...
}
  • Are you sure you're not swallowing an exception somewhere? Have you tired some HTTP debugging? With Fiddler, for example? – Paulo Morgado May 26 '13 at 22:53

3 Answers3

1

I had the same problem. After hours and hours on end i found out that the problem was in a stream which was not disposed. I used it to check weather a file exists in a web directory.

    private bool WebFileExists(string url)
    {
        bool exists = true;

        try
        {
            using (WebClient testClient = new WebClient())
            {
                testClient.Credentials = new NetworkCredential(_userName, _password);
                Stream stream = testClient.OpenRead(url);
                stream.Dispose();
            }
        }
        catch
        {
            exists = false;
        }

        return exists;
    }
SubqueryCrunch
  • 1,325
  • 11
  • 17
0

Actually I replicated the same code you have in a WPF client. I reproduced the problem but from the 1st file. I found a 0 bytes length file created locally but no content.

In my case the solution I added was to set the user credentials, after which I was able to download multiple files, and repeatedly (stepping on previous downloads).

I added this line:wc.Credentials = CredentialCache.DefaultCredentials;

private void Download(string url, string downloadDirectory, string fileName)
{
    WebClient wc = new WebClient();
    wc.Credentials = CredentialCache.DefaultCredentials;

    wc.DownloadProgressChanged += DownloadProgress;
    wc.DownloadFileCompleted += DownloadComplete;
    wc.DownloadFileAsync(new Uri(url), downloadDirectory + fileName);
}
Ashraf ElSwify
  • 192
  • 1
  • 8
0

I managed to fix the problem by passing the instance of WebClient that was downloading the file through the EventHandler's parameters and then using that to get the original filename, rather than using a seperate instance of it to retrieve the filename.

Credit to this question for helping me figure out the solution.

With this solution, because I can't find out the original filename until after the file has been downloaded, I assign it a temporary name for the download, and rename it when the download is complete in the AsyncCompletedEventHandler.

private void Download(string url, string downloadDirectory)
{
    WebClient wc = new WebClient();

    wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
    wc.DownloadFileCompleted += new AsyncCompletedEventHandler((sender, e) => DownloadComplete(sender, e, wc));

    wc.DownloadFileAsync(new Uri(url), downloadDirectory + "temp");
}

private void DownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
    downloadProgressBar.Value = e.ProgressPercentage;
}

private void DownloadComplete(object sender, AsyncCompletedEventArgs e, WebClient wc)
{
    string filename = new ContentDisposition(wc.ResponseHeaders["Content-Disposition"].ToString()).FileName;
    if (File.Exists(downloadDirectory + "temp"))
    {
        if (File.Exists(downloadDirectory + filename))
            File.Delete(downloadDirectory + filename);
        File.Move(downloadDirectory + "temp", downloadDirectory + filename);
    }

    // ...
}
Community
  • 1
  • 1
  • This is by far better than your original solution ... your 'GetFileNameFromUrl' method was starting the file download from the server, but never fetching the file content ... a costly server roundtrip, and an abandoned web request that gets disposed of who-knows when (when the garbage collector goes to get it). To improve this further use the TaskAsync overloads in .NET 4.5 and the TAP workflow instead of the "EventHandler" approach. – TCC May 02 '14 at 20:36