2

I have an ASP.Net website where I am downloading a large zip file to the server from a remote site. This file is not transferred to the client, but will remain on the server. I would like to provide progress updates to the user using SignalR. When I use the code below:

public class InstallController : Hub
{
    public void Send( string message )
    {
        Clients.All.AddMessage( message );
    }

    public void FileDownload()
    {
        WebClient client = new WebClient();
        client.DownloadProgressChanged += new DownloadProgressChangedEventHandler( client_DownloadProgressChanged );
        client.DownloadFileCompleted += new AsyncCompletedEventHandler( client_DownloadFileCompleted );
        client.DownloadFileAsync( new Uri( "http://someserver.com/install/file.zip" ), @"\file.zip" );

    }

    /* callbacks for download */
    void client_DownloadProgressChanged( object sender, DownloadProgressChangedEventArgs e )
    {
        double bytesIn = double.Parse( e.BytesReceived.ToString() );
        double totalBytes = double.Parse( e.TotalBytesToReceive.ToString() );
        double percentage = bytesIn / totalBytes * 100;

        this.Send( String.Format( "Download progress: {0}%", percentage.ToString() ) );
    }

    void client_DownloadFileCompleted( object sender, AsyncCompletedEventArgs e )
    {
        this.Send( "Finished downloading file..." );
    }     
}

I get the exception:

An exception of type 'System.InvalidOperationException' occurred in System.Web.dll but was not handled in user code

Additional information: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.

I've seen several mentions to use the HttpClient instead of the WebClient, but I don't see how to get the progress from that.

J. Lennon
  • 3,311
  • 4
  • 33
  • 64
Jon Edmiston
  • 950
  • 1
  • 9
  • 17
  • can you post the full code : at least the method declaration in which this code reside ? – Zaid Al-Omari May 17 '14 at 23:48
  • also you should not use signalR for that issue.. and could you clarify this sentence : _I am downloading a large zip file to the server from a remote site_ . why would you download the file in the server when you just want the client to download it ! provide him the link instead. – Zaid Al-Omari May 17 '14 at 23:51
  • The file will remain on the server. It's for the install of server components. – Jon Edmiston May 18 '14 at 03:10
  • Are you downloading file on button click event?? Can you please share structure of your .aspx page?? I want to download file on button click event and show real time progress bar in my application. Is it happen using your above code?? – Keval Gangani Mar 30 '15 at 13:19

1 Answers1

2

"It's All About the SynchronizationContext"
http://msdn.microsoft.com/en-us/magazine/gg598924.aspx
This phrase is becoming quite common since the addition of new technology and features in .NET.

Briefly.. There are several components, such as BackgroundWorker and WebClient, thats hiding the SynchronizationContext to the capture and usage, it means that you need to respect the life cycle of requests, the life cycle of ASP.NET components.

Speaking specifically, the HTTP methods (GET and POST) always keep working in the same way, the client submits a HTTP request to the server, then the server returns a response to the client, and the application will try to ensure that this occurs, the SynchronizationContext of ASP.NET was designed for this.

More information:

Even the requests using SignalR contains the same ASP.NET SynchronizationContext, because of it you need to work "outside" the current SynchronizationContext or use it in the right way.

SignalR was designed to use asynchronous programming, using TPL as default, you can take benefits of it, check in http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#asyncmethods and http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#asyncclient

You can solve your problem in many ways.
If you want to use SignalR to show the progress.. I would do something like the code below (I'm still using .NET 4.0, bu it is more easy with .NET 4.5 with TaskAsync methods).

public Task<string> FileDownload()
{
    var client = new WebClient();
    client.DownloadProgressChanged += (sender, args) => client_DownloadProgressChanged(sender, args, this.Context.ConnectionId);
    client.DownloadFileAsync(new Uri("https://epub-samples.googlecode.com/files/cc-shared-culture-20120130.epub"), @"C:\temp\file.zip");

    var result = new TaskCompletionSource<string>();
    AsyncCompletedEventHandler clientOnDownloadFileCompleted = (sender, args) =>
    {
        client.Dispose();
        if (args.Error != null)
        {
            result.SetException(args.Error); //or result.SetResult(args.Error.Message);
            return;
        }
        result.SetResult("Downloaded");
    };

    client.DownloadFileCompleted += clientOnDownloadFileCompleted;
    return result.Task;
}

private static void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e,
                                                   string connectionId)
{
    GlobalHost.ConnectionManager.GetHubContext<SomeHub>()
              .Clients.Client(connectionId)
              .NotifyProgress(e.ProgressPercentage);
}

Keep in mind that this is just an example, you could improve the way they treat the disconnection, and cancellation, among other things that can occur (depends on your application logic).

Also it is possible to use a "workaround" (not recommended):

The code would be very similar to the above.

Community
  • 1
  • 1
J. Lennon
  • 3,311
  • 4
  • 33
  • 64
  • I get the same exception "An exception of type 'System.InvalidOperationException' occurred in System.Web.dll but was not handled in user code" on the line client.DownloadFileAsync(new Uri("https://epub-samples.googlecode.com/files/cc-shared-culture-20120130.epub"), @"C:\temp\file.zip"); – Jon Edmiston May 20 '14 at 06:03
  • This will depend on what time you are calling that method .. I have written him to be called directly by SignalR client. – J. Lennon May 20 '14 at 11:28
  • I get the same exception when calling it from the client directly. – Jon Edmiston May 20 '14 at 15:15
  • @edmistj this is unexpected behavior, you can try throw the WebClient code inside: Task.Factory.StartNew(() => { /* code here */ }); – J. Lennon May 20 '14 at 17:50
  • Thank you that worked. In the end I think I might go with this solution http://forums.xamarin.com/discussion/2022/threading-help, but I appreciate your time and it did work. – Jon Edmiston May 21 '14 at 06:16
  • @J.Lennon I have create "SomeHub" Hub class in my application. Can you let me know how can I call above "FileDownload" method on my button click event??? – Keval Gangani Mar 30 '15 at 15:40
  • @Dexterity Since you are using SignalR you need to write some javascript code. There is no way to do it using just get/post (web forms or mvc) because the general idea is keep track of progress (no refresh screen). – J. Lennon Mar 31 '15 at 08:35