10

I'm trying to make a UI for downloading files from my site. The site have zip-files and these need to be downloaded to the directory entered by the user. However, I can't succeed to download the file, it just opens up from a temporary folder.

Code:

private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
        e.Cancel = true;
        string filepath = null;
            filepath = textBox1.Text;
            WebClient client = new WebClient();
            client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
            client.DownloadFileAsync(e.Url, filepath);
}

Full sourcecode: http://en.paidpaste.com/LqEmiQ

Dulini Atapattu
  • 2,735
  • 8
  • 33
  • 47
Victor Bjelkholm
  • 2,177
  • 9
  • 28
  • 50
  • 1
    Post relevant parts of your code here, directly in the question. Don't use external links. – Anders Abel Jul 21 '11 at 09:21
  • Are you trying to override the browser's implementation of the save file UI? It would be much easier if you write the file to the stream and specify in content type that it is a file... Also if i remember correctly u dont have access to ur client's filesystem...u cannot download files to paths on client hence taking it as an input is not of any use.. – Whimsical Jul 21 '11 at 09:27
  • I -think- this is a client software, i.e. it runs in process on the client's machine. – J. Steen Jul 21 '11 at 09:50
  • What is the value of textBox1.Text? – Bob Vale Jul 21 '11 at 09:51
  • @Mulki, I don't know how to do that... – Victor Bjelkholm Jul 21 '11 at 22:39
  • @Bob Vale, It's a local path that the user set. – Victor Bjelkholm Jul 21 '11 at 22:39
  • @Victor, Yes but can you put a break point in and inspect the values with an example that doesn't work and confirm that the value is actually what you expect it to be. – Bob Vale Jul 21 '11 at 23:59
  • I don't even know how to detect when the "Save file"-dialog appears. I want to hide it and automatically save the file. – Victor Bjelkholm Jul 22 '11 at 09:47
  • So you're talking about an embedded internet explorer control(i.e. the `WebBrowser` class)? Perhaps you should have said that explicitly. While it can be seen from your method name, I for one missed that at first. – CodesInChaos Jul 24 '11 at 12:25
  • @CodeInChaos, sorry for that, thought it was self explaining when the method name is webBrowser1_Navigating. – Victor Bjelkholm Jul 24 '11 at 12:59

6 Answers6

20

If you don't want to use "WebClient" or/and need to use the System.Windows.Forms.WebBrowser e.g. because you want simulate a login first, you can use this extended WebBrowser which hooks the "URLDownloadToFile" Method from the Windows URLMON Lib and uses the Context of the WebBrowser

Infos: http://www.pinvoke.net/default.aspx/urlmon/URLDownloadToFile%20.html

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace dataCoreLib.Net.Webpage
{
        public class WebBrowserWithDownloadAbility : WebBrowser
        {
            /// <summary>
            /// The URLMON library contains this function, URLDownloadToFile, which is a way
            /// to download files without user prompts.  The ExecWB( _SAVEAS ) function always
            /// prompts the user, even if _DONTPROMPTUSER parameter is specified, for "internet
            /// security reasons".  This function gets around those reasons.
            /// </summary>
            /// <param name="callerPointer">Pointer to caller object (AX).</param>
            /// <param name="url">String of the URL.</param>
            /// <param name="filePathWithName">String of the destination filename/path.</param>
            /// <param name="reserved">[reserved].</param>
            /// <param name="callBack">A callback function to monitor progress or abort.</param>
            /// <returns>0 for okay.</returns>
            /// source: http://www.pinvoke.net/default.aspx/urlmon/URLDownloadToFile%20.html
            [DllImport("urlmon.dll", CharSet = CharSet.Auto, SetLastError = true)]
            static extern Int32 URLDownloadToFile(
                [MarshalAs(UnmanagedType.IUnknown)] object callerPointer,
                [MarshalAs(UnmanagedType.LPWStr)] string url,
                [MarshalAs(UnmanagedType.LPWStr)] string filePathWithName,
                Int32 reserved,
                IntPtr callBack);


            /// <summary>
            /// Download a file from the webpage and save it to the destination without promting the user
            /// </summary>
            /// <param name="url">the url with the file</param>
            /// <param name="destinationFullPathWithName">the absolut full path with the filename as destination</param>
            /// <returns></returns>
            public FileInfo DownloadFile(string url, string destinationFullPathWithName)
            {
                URLDownloadToFile(null, url, destinationFullPathWithName, 0, IntPtr.Zero);
                return new FileInfo(destinationFullPathWithName);
            }
        }
    }
dataCore
  • 1,509
  • 1
  • 16
  • 17
  • 3
    You are the man! Ive spent 3 days looking exactly for that kind of solution ! – Maciej Jan 05 '17 at 23:31
  • 1
    Perfect answer. – ujjaval Mar 27 '17 at 07:43
  • 1
    This solution is perfect. I was hitting issues with this when trying to download a file from a Laravel site behind authentication, and Auth::user() returned null when using the WebClient download. – Zachary Canann Apr 07 '17 at 19:15
  • 1
    @dataCore Thank you so much! very helpful. No prompts. I love the use of p/Invoke. : ) – Leo Gurdian Sep 13 '17 at 00:12
  • How to get event when it finish download? – ihorko Feb 25 '18 at 11:54
  • @ihorko: its not async - the dowloaded file will be returned by the FileInfo. You can play with the "IntPtr callBack" to receive some information about the download. But you have to write your oven Eventhandler (e.g. for a "progressChanged" or "downloadComplete" – dataCore Feb 26 '18 at 16:06
  • Could give me a simple how to use this? I created a browser of dataCoreLib.Net.Webpage.WebBrowserWithDownloadAbility type, loaded a page with this HTML `Click me!` so when I click on this a href, the download dialog box shows up normally. Isn't the download automatic? – Jack Sep 12 '18 at 16:20
  • 1
    This was exactly what i needed. – Kevbo Dec 14 '18 at 00:47
  • 1
    Incredible, I've spent more than a week searching for something like this. You've saved my day. – RolandDeschain Jun 05 '19 at 09:58
  • 1
    Fantastic!!! Thank you so much. – Namachi R Nov 08 '21 at 20:50
20

My program does exactly what you are after, no prompts or anything, please see the following code.

This code will create all of the necessary directories if they don't already exist:

Directory.CreateDirectory(C:\dir\dira\dirb);  // This code will create all of these directories  

This code will download the given file to the given directory (after it has been created by the previous snippet:

private void install()
    {
        WebClient webClient = new WebClient();                                                          // Creates a webclient
        webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);                   // Uses the Event Handler to check whether the download is complete
        webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);  // Uses the Event Handler to check for progress made
        webClient.DownloadFileAsync(new Uri("http://www.com/newfile.zip"), @"C\newfile.zip");           // Defines the URL and destination directory for the downloaded file
    }

So using these two pieces of code you can create all of the directories and then tell the downloader (that doesn't prompt you to download the file to that location.

  • What should be done if this file is being fetched from a protected folder on server? In that case you can not fetch file directly from server but if the file has been processed by the server to download and send back to the browser. In that case how could one save that file without showing save file prompt? Any suggestions?? – Vipin Kr. Singh Nov 30 '14 at 09:44
  • @VipinKr.Singh that is the exact same scenario that I'm facing. – Leo Gurdian Sep 12 '17 at 23:26
15

Why not just bypass the WebClient's file handling pieces altogether. Perhaps something similar to this:

    private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
    {
        e.Cancel = true;
        WebClient client = new WebClient();

        client.DownloadDataCompleted += new DownloadDataCompletedEventHandler(client_DownloadDataCompleted);

        client.DownloadDataAsync(e.Url);
    }

    void client_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
    {
        string filepath = textBox1.Text;
        File.WriteAllBytes(filepath, e.Result);
        MessageBox.Show("File downloaded");
    }
SamuelWarren
  • 1,449
  • 13
  • 28
2

Well, your solution almost works. There are a few things to take into account to keep it simple:

  • Cancel the default navigation only for specific URLs you know a download will occur, or the user won't be able to navigate anywhere. This means you musn't change your website download URLs.

  • DownloadFileAsync doesn't know the name reported by the server in the Content-Disposition header so you have to specify one, or compute one from the original URL if that's possible. You cannot just specify the folder and expect the file name to be retrieved automatically.

  • You have to handle download server errors from the DownloadCompleted callback because the web browser control won't do it for you anymore.

Sample piece of code, that will download into the directory specified in textBox1, but with a random file name, and without any additional error handling:

private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e) {
    /* change this to match your URL. For example, if the URL always is something like "getfile.php?file=xxx", try e.Url.ToString().Contains("getfile.php?") */
    if (e.Url.ToString().EndsWith(".zip")) {
        e.Cancel = true;
        string filePath = Path.Combine(textBox1.Text, Path.GetRandomFileName());
        var client = new WebClient();
        client.DownloadFileCompleted += client_DownloadFileCompleted;
        client.DownloadFileAsync(e.Url, filePath);
    }
}

private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) {
    MessageBox.Show("File downloaded");
}

This solution should work but can be broken very easily. Try to consider some web service listing the available files for download and make a custom UI for it. It'll be simpler and you will control the whole process.

Julien Lebosquain
  • 40,639
  • 8
  • 105
  • 117
  • I also tried to make a custom UI but didn't succeed. Still learning at a very basic level and get stuck all the time. But thanks for your time and help, I'll return with a status update. – Victor Bjelkholm Jul 24 '11 at 13:03
  • It is possible to retrive file name from the URL, just try this `string filename = Path.GetFileName(e.Url.LocalPath)` – rotman Jul 24 '11 at 13:04
  • Yes, but there are many cases where it wouldn't make sense, especially if the download URL is in the form "download?id=x", you'll get "download" for every file name. – Julien Lebosquain Jul 24 '11 at 13:11
1

Take a look at http://www.csharp-examples.net/download-files/ and msdn docs on webclient http://msdn.microsoft.com/en-us/library/system.net.webclient.aspx

My suggestion is try the synchronous download as its more straightforward. you might get ideas on whether webclient parameters are wrong or the file is in incorrect format while trying this.

Here is a code sample..

private void btnDownload_Click(object sender, EventArgs e)
{
  string filepath = textBox1.Text;
  WebClient webClient = new WebClient();
  webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
  webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);
  webClient.DownloadFileAsync(new Uri("http://mysite.com/myfile.txt"), filepath);
}

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

private void Completed(object sender, AsyncCompletedEventArgs e)
{
  MessageBox.Show("Download completed!");
}
Whimsical
  • 5,985
  • 1
  • 31
  • 39
  • the problem is that I want my application to download the file, not Internet Explorer that runs with webbrowser. When I click an link of a zip-file, the save-file popup appears and want me to chose things. I want the file to be downloaded automatically when a user clicks on a zip. – Victor Bjelkholm Jul 24 '11 at 11:11
0

A much simpler solution would be to download the file using Chrome. In this manner you don't have to manually click on the save button.

using System;
using System.Diagnostics;
using System.ComponentModel;

namespace MyProcessSample
{
    class MyProcess
    {
        public static void Main()
        {
            Process myProcess = new Process();
            myProcess.Start("chrome.exe","http://www.com/newfile.zip");
        }
    }
}