C# download file with progress indicator and then install file

Go To StackoverFlow.com

0

I'm trying to write a C# Windows application which downloads a file (waits for that download to complete - providing some indicator of progress) and then executes the downloaded file. I think I either need:

  • A synchronous download that has a progress indicator AND doesn't make my application unresponsive
  • An asynchronous download that waits for the download to complete and then attempts to execute it.

The former (synchronous download) seems to do what I want but I can't find a way to indicate progress any way and it appears to make my program non-responsive (like it's hung) - which may cause users to close the application instead of waiting for it to finish downloading.

The later (asynchronous download) I've been able to accomplish with a progress indicator etc but what happens is that the download kicks off and my application immediately tries to install it before its finished downloading which of course fails.

So whats the best way to accomplish this? Below is the code I presently have for the async download with progress bar.

Edit 4/10/2012

The old code was getting to cumbersome to work around so here is what I presently have. It works except that it doesn't update the progress bar.

    private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {

            List<App> FilesToDownload = e.Argument as List<App>;

            foreach (App Program in FilesToDownload) // Loop through List with foreach
            {
                //string Text = ;
                UpdateRichTextBox("Downloading " + Program.Filename + "... Please wait");

                string Source = Program.DownloadLocation + "/" +  Program.Filename;
                string Destination = System.AppDomain.CurrentDomain.BaseDirectory + Program.Filename;

                WebClient webClient = new WebClient();
                webClient.DownloadFile(Source, @Destination);
            }

            UpdateRichTextBox("File download complete");

            foreach (App Program in FilesToDownload) // Loop through List with foreach
            {
                string Filename = Program.Filename;
                string Arguments = Program.InstallParameters;

                if (Filename != "advisorinstaller.exe")
                {
                    Process p = new Process();
                    p.StartInfo.FileName = System.AppDomain.CurrentDomain.BaseDirectory + Filename;
                    p.StartInfo.Arguments = Arguments;
                    p.Start();
                    p.WaitForExit();
                }
                else
                {
                    MessageBox.Show("About to install Belarc Advisor. This may take 10-15 minutes to run - do not shutdown this program. Click OK when ready to proceed.");
                }
            }
    }

    private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        UpdateRichTextBox("Install Complete");

    }

Edit 4/11/2012

So I have edited my backgroundworker do work as follows:

        private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {

            List<App> FilesToDownload = e.Argument as List<App>;

            int counter = 0;
            int percent = 0;

            foreach (App Program in FilesToDownload) // Loop through List with foreach
            {
                percent = ((counter / FilesToDownload.Count) * 100);
                backgroundWorker1.ReportProgress(percent, "");

                UpdateRichTextBox("Downloading " + Program.Filename + "... Please wait");

                string Source = Program.DownloadLocation + "/" +  Program.Filename;
                string Destination = System.AppDomain.CurrentDomain.BaseDirectory + Program.Filename;

                WebClient webClient = new WebClient();
                webClient.DownloadFile(Source, @Destination);


                counter++;

            }

            //backgroundWorker1.ReportProgress(100, "Complete!");
    }

If I uncomment the last line the progress bar proceeds to 100%. But its not progressing using:

percent = ((counter / FilesToDownload.Count) * 100);
backgroundWorker1.ReportProgress(percent, "");

Any ideas why?

2012-04-04 18:43
by Brad
Where's the code that does the execute? Would think this would be handled in the client_DownloadFileCompleted handler - itsmatt 2012-04-04 19:02
A user presses a button which checks if certain checkboxes are checked, then calls methods to do the installation. The installation methods in turn call the download file method followed by an attempt to install the downloaded file. I'll see if i can post an example - Brad 2012-04-04 20:26
Hmm, so why are you doing the Process stuff outside the Completed handler ? Seems like that would be the place to do the work or kick off another thread to do the work. You'd mentioned earlier that trying to execute before the download fails (of course), so why not just throw that execute into the method that indicates that the download is done - itsmatt 2012-04-04 21:19


1

You can use a BackgroundWorker to perform a synchroneous download in the background. It supports progress reporting as well. This will avoid blocking your UI (because the download is running on a different thread).

2012-04-04 18:57
by ChrisWue
I'm trying to do this but I'm not having a lot of luck. I've added a Background Worker. And created the following DoWork, ProgressChanged and RunWorker Completed methods. However how can I pass the source and destination to the DoWork method? I'm downloadig several files depending on what the user has changed in the application so I can't hardcode them. I need to pass them as parameters - Brad 2012-04-04 20:23
Chris - This seems to work my application no longer "locks up" but it isn't updating my progress bar either. Any suggestions on how I might fix that? I'm posting my updated code above - Brad 2012-04-10 19:26
@Brad: Not sure if that is just a copy-and-paste error but the code you posted doesn't subscribe to the ProgressChanged event handler - ChrisWue 2012-04-10 19:32
I have a method called btnBeginClick1 which calls the background worker and I think should update progress changed? Here's my code: backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged; backgroundWorker1.RunWorkerAsync(FilesToDownload) - Brad 2012-04-11 12:42


0

You can use the ManualResetEvent class to wait on your main thread (using WaitOne()) until your client_DownloadFileCompleted() event handler is called (which calls Set() on your object).

2012-04-04 19:01
by Philipp Schmid


0

Have you considered using ClickOnce to do this? It will solve your problem along with auto updates.

Check out this SO questions for places to get started: https://stackoverflow.com/questions/1635103/how-to-basic-tutorial-to-write-a-clickonce-app.

2012-04-04 19:03
by Philipp Schmid
I'm not planning on deploying any updates - this is a one time install. Additionally the application does more than just install (it copies config files etc). So I'm not sure this is a good solutio - Brad 2012-04-04 20:22


0

No need to wrap the WebClient in a BackgroundWorker as it is already asynchronous (using a thread from the thread-pool).

Note: The sample below was written in the editor, whilst I checked the MSDN documentation for the method signatures I could have easily have made a mistake.

public void InstallSecuniaPSI()
{
    var source = new Uri("http://www.webserver.com:8014/psisetup.exe");
    var dest = Path.Combine(Path.GetTemp(), "Download_SecuniaPSI.exe");

    var client = new WebClient();
    client.DownloadProgressChanged += OnDownloadProgressChanged;
    client.DownloadFileCompleted += OnDownloadComplete;
    client.DownloadFileAsync(source, dest, dest);   
}

private void OnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    // report progress
    Console.WriteLine("'{0}' downloaded {1} of {2} bytes. {3}% complete"(string)e.UserState, e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage);
}

private void OnDownloadComplete(object sender, AsyncCompletedEventArgs e)
{
    // clean up
    var client = (WebClient)sender;
    client.DownloadProgressChanged -= OnDownloadProgressChanged;
    client.DownloadFileCompleted -= OnDownloadComplete;

    if (e.Error != null)
        throw e.Error;

    if (e.Cancelled)
        Environment.Exit(1);

    // install program
    var downloadedFile = (string)e.UserState;
    var processInfo = new ProcessStartInfo(downloadedFile, "/S");
    processInfo.CreateNoWindow = true;

    var installProcess = Process.Start(processInfo);
    installProcess.WaitForExit();

    Environment.Exit(installProcess.ExitCode);
}

The only thing that needs to be sorted now is how to make the program wait; if it is a console program then call Console.ReadKey() after the InstallSecuniaPSI().

HTH,

2012-04-04 22:37
by Dennis
Ads