Arvo Bowen
Arvo Bowen

Reputation: 4945

How can I get more information from WebClient.DownloadDataAsync() while it's connecting?

I use this to download a file...

WebClient wc = new WebClient();
wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
wc.DownloadDataCompleted +=new DownloadDataCompletedEventHandler(wc_DownloadDataCompleted);
wc.DownloadDataAsync(new Uri("http://www.example.com/myfile.exe"));

I have my event handlers like this...

private void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    //There has been some type of progress with the download
}

private void wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
    //The download has finished
}

Everything works great! The issue I have is the delay when I call DownloadDataAsync(). Once I call that it seems I have no type of progress reporting at all until the WebClient downloads the first chunk of data. In that time the UI looks like it is waiting on something like user input etc...

Is there any way to get more information about what's going on in this timeframe? I'm sure it's resolving a host, sending user/pass info, etc. I'm looking to get some of that progress so I can update my UI more so.

Upvotes: 1

Views: 412

Answers (1)

rene
rene

Reputation: 42493

If you don't want to re-implement the default WebClient to send events when a DNS lookup is done, a secure channel is negotiated, a socket is being opened en connected, http-headers are exchanged, to name a few, you could in the scenario with a winclient maybe abuse the built in logging a bit.

The idea is as follows: You configure a custom TraceListener that will be fed by the message being logged by a System.Net TraceSource. If you configure the tracesource to verbose logging, you get messages that gives you an indication what the progress is of any ongoing connections. The listener raises events for every message that is received which you can subscribe to in your application.

Here is the implementation of the TraceListener:

/// <summary>
/// holds the message being send to the listener
/// </summary>
public class DetailedProgressEventArgs:EventArgs
{
    public string Message;
    public DetailedProgressEventArgs(string msg)
    {
        Message = msg;
    }
}

/// <summary>
/// enables sending events when a log message is written
/// </summary>
/// <param name="sender">the instance that sends the event</param>
/// <param name="e">the actual message</param>
public delegate void DetailedProgressEventHandler(object sender, DetailedProgressEventArgs e);

/// <summary>
/// Used as a TraceListener and wired in the app.config to receive System.Net messages
/// </summary>
public class DetailedProgressListener : TraceListener
{
    // wire the event handlers here
    static public event DetailedProgressEventHandler ProgressChanged;

    public override void Write(string message)
    {
        // let's do nothing
    }

    /// <summary>
    /// rasies a progessChanged event for every message
    /// </summary>
    /// <param name="message"></param>
    public override void WriteLine(string message)
    {
        var pch = ProgressChanged;
        if (pch!=null)
        {
            pch.Invoke(this, new DetailedProgressEventArgs(message));
        }
    }
}

And this is what you need to add to your app.config to get the diagnostic logging enabled:

<configuration>
    <system.diagnostics>
      <sources>
        <source name="System.Net" tracemode="includehex" maxdatasize="1024">
          <listeners>
            <add name="ProgressListener"/>
          </listeners>
        </source>
      </sources>
      <switches>
        <add name="System.Net" value="Verbose"/>
      </switches>
      <sharedListeners>
        <add name="ProgressListener"
          type="YourApp.DetailedProgressListener, YourApp" />
      </sharedListeners>
      <trace autoflush="true"/>
    </system.diagnostics>
</configuration>

I created a small implementation example for a console application. I assume this gives enough details to adapt it to your own implementation scenario, either using a multi-line textbox or a progressbar:

Console.BackgroundColor = ConsoleColor.Black;

// handle hidden progress
DetailedProgressEventHandler progress = (s, e) =>
    {
        var old = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.Write("+");
        Debug.WriteLine(e.Message);
        Console.ForegroundColor = old;
    };

// hookup the event
DetailedProgressListener.ProgressChanged += progress;

using(var wc = new WebClient())
{
    wc.DownloadProgressChanged += (s, e) => {
        // stop once we have normal progress
        DetailedProgressListener.ProgressChanged -= progress;

        var old = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.DarkRed;
        Console.Write(e.BytesReceived);
        Console.Write(" ,");
        Console.ForegroundColor = old;
    };
    wc.DownloadDataCompleted += (s, e) =>
    {
        var old = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine(" Done");
        Console.ForegroundColor = old;
    };

    wc.DownloadDataAsync(new Uri("https://stackoverflow.com"));
}
Console.ReadLine();

My output looks like this:

output from progress class

the yellow + are written when the TraceListener was invoked. The rest is data received from the normal events raised by the WebClient.

Keep in mind that the Listener is global for the application. So if you have background threads that use the System.Net classes as well you'll also get events for those. Either parse the messages to achieve some correlation or if that becomes to fragile, implement your own WebClient with additional ProgressEvents.

Upvotes: 1

Related Questions