从URL路径下载文件的简单方法是什么?


当前回答

试试这个:

private void downloadFile(string url)
{
     string file = System.IO.Path.GetFileName(url);
     WebClient cln = new WebClient();
     cln.DownloadFile(url, file);
}

其他回答

WebClient已经过时了

如果你想下载到一个文件,通过使用ResponseHeadersRead避免第一次读取到内存,就像这样:

static public async Task HttpDownloadFileAsync(HttpClient httpClient, string url, string fileToWriteTo) {
  using HttpResponseMessage response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
  using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync(); 
  using Stream streamToWriteTo = File.Open(fileToWriteTo, FileMode.Create); 
  await streamToReadFrom.CopyToAsync(streamToWriteTo);
}

以上代码更多的是一个大纲,添加正确的错误/异常处理是不平凡的,进度报告也是不平凡的,因为是处置。

我提出了一组c# 9.0扩展类downadfileasync, GetToStringAsync和PostToStringAsync

namespace System.Net.Http {

  // HttpResponse is in one of 3 states:
  // - ResponseMessageInfo is object && ResponseMessageInfo.IsSuccessStatusCode -> success, inspect ResponseMessageInfo for StatusCode etc
  // - ResponseMessageInfo is object && !ResponseMessageInfo.IsSuccessStatusCode -> failure, inspect ResponseMessageInfo for StatusCode, ReasonPhrase etc
  // - ResponseMessageInfo is null -> exception, inspect ExceptionInfo fields
  public record HttpResponse {

    // copies of HttpRequestMessage and HttpResponseMessage which do not have the content and do not need to be disposed
    public record HttpRequestMessageInfo(HttpRequestHeaders Headers, HttpMethod Method, HttpRequestOptions Options, Uri? RequestUri, Version Version, HttpVersionPolicy VersionPolicy);
    public record HttpResponseMessageInfo(HttpResponseHeaders Headers, bool IsSuccessStatusCode, string? ReasonPhrase, HttpRequestMessageInfo RequestMessage, HttpStatusCode StatusCode, HttpResponseHeaders TrailingHeaders, Version Version);

    // holds Http exception information
    public record HttpExceptionInfo(HttpRequestMessageInfo HttpRequestMessage, string ErrorMessage, WebExceptionStatus? WebExceptionStatus);

    // if ResponseMessageInfo is null ExceptionInfo is not and vice versa
    public HttpResponseMessageInfo? ResponseMessageInfo { get; init; }
    public HttpExceptionInfo? ExceptionInfo { get; init; }

    public HttpResponse(HttpRequestMessage requestMessage, HttpResponseMessage responseMessage) {
      var requestMessageInfo = new HttpRequestMessageInfo(requestMessage.Headers, requestMessage.Method, requestMessage.Options, requestMessage.RequestUri, requestMessage.Version, requestMessage.VersionPolicy);
      ResponseMessageInfo = new(responseMessage.Headers, responseMessage.IsSuccessStatusCode, responseMessage.ReasonPhrase, requestMessageInfo, responseMessage.StatusCode, responseMessage.TrailingHeaders, responseMessage.Version);
      ExceptionInfo = null;
    }

    public HttpResponse(HttpRequestMessage requestMessage, Exception exception) {
      ResponseMessageInfo = null;
      var requestMessageInfo = new HttpRequestMessageInfo(requestMessage.Headers, requestMessage.Method, requestMessage.Options, requestMessage.RequestUri, requestMessage.Version, requestMessage.VersionPolicy);

      if (exception is WebException ex1 && ex1.Status == WebExceptionStatus.ProtocolError) {
        using HttpWebResponse? httpResponse = (HttpWebResponse?)ex1.Response;
        ExceptionInfo = new(requestMessageInfo, httpResponse?.StatusDescription ?? "", ex1.Status);
      } 
      else if (exception is WebException ex2) ExceptionInfo = new(requestMessageInfo, ex2.FullMessage(), ex2.Status);
      else if (exception is TaskCanceledException ex3 && ex3.InnerException is TimeoutException) ExceptionInfo = new(requestMessageInfo, ex3.InnerException.FullMessage(), WebExceptionStatus.Timeout);
      else if (exception is TaskCanceledException ex4) ExceptionInfo = new(requestMessageInfo, ex4.FullMessage(), WebExceptionStatus.RequestCanceled);
      else ExceptionInfo = new(requestMessageInfo, exception.FullMessage(), null);
    }

    public override string ToString() {
      if (ResponseMessageInfo is object) {
        var msg = ResponseMessageInfo.IsSuccessStatusCode ? "Success" : "Failure";
        msg += $" {Enum.GetName(typeof(HttpStatusCode), ResponseMessageInfo.StatusCode)}";
        if (ResponseMessageInfo.ReasonPhrase is object) msg += $" {ResponseMessageInfo.ReasonPhrase}";
        return msg;

      } else if (ExceptionInfo is object) {
        var msg = "Failure";
        msg += $" {ExceptionInfo.ErrorMessage}";
        if (ExceptionInfo.WebExceptionStatus is object) msg += $" {Enum.GetName(typeof(WebExceptionStatus), ExceptionInfo.WebExceptionStatus)}";
        return msg;
      }
      return "NA"; // never reach here
    }
  }


  public static class ExtensionMethods {

    // progressCallback recieves (bytesRecieved, percent, speedKbSec) and can return false to cancell download
    public static async Task<(bool success, HttpResponse httpResponse)> DownloadFileAsync(this HttpClient httpClient, Uri requestUri, string fileToWriteTo, CancellationTokenSource? cts = null, Func<long, int, float, bool>? progressCallback = null) {
      var httpRequestMessage = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = requestUri };
      var created = false;

      try {
        var cancellationToken = cts?.Token ?? default;

        using HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
        if (!httpResponseMessage.IsSuccessStatusCode) return (false, new(httpRequestMessage, httpResponseMessage));
        var contentLength = httpResponseMessage.Content.Headers.ContentLength;

        using Stream streamToReadFrom = await httpResponseMessage.Content.ReadAsStreamAsync();
        using Stream streamToWriteTo = File.Open(fileToWriteTo, FileMode.Create);
        created = true;

        var buffer = new byte[81920]; 
        var bytesRecieved = (long)0;
        var stopwatch = Stopwatch.StartNew();
        int bytesInBuffer;
        while ((bytesInBuffer = await streamToReadFrom.ReadAsync(buffer, cancellationToken)) != 0) {
          await streamToWriteTo.WriteAsync(buffer.AsMemory(0, bytesInBuffer), cancellationToken);
          bytesRecieved += bytesInBuffer;
          if (progressCallback is object) {
            var percent = contentLength is object && contentLength != 0 ? (int)Math.Floor(bytesRecieved / (float)contentLength * 100.0) : 0;
            var speedKbSec = (float)((bytesRecieved / 1024.0) / (stopwatch.ElapsedMilliseconds / 1000.0));
            var proceed = progressCallback(bytesRecieved, percent, speedKbSec);
            if (!proceed) {
              httpResponseMessage.ReasonPhrase = "Callback cancelled download";
              httpResponseMessage.StatusCode = HttpStatusCode.PartialContent;
              return (false, new(httpRequestMessage, httpResponseMessage));
            }
          }
        }

        return (true, new(httpRequestMessage, httpResponseMessage));
      }
      catch (Exception ex) {
        if (created) try { File.Delete(fileToWriteTo); } catch { };
        return (false, new(httpRequestMessage, ex));
      }
    }

    public static async Task<(string? ResponseAsString, HttpResponse httpResponse)> GetToStringAsync(this HttpClient httpClient, Uri requestUri, CancellationTokenSource? cts = null) {
      var httpRequestMessage = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = requestUri };
      try {
        var cancellationToken = cts?.Token ?? default;
        using var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage, cancellationToken);
        if (!httpResponseMessage.IsSuccessStatusCode) return (null, new(httpRequestMessage, httpResponseMessage));
        
        var responseAsString = await httpResponseMessage.Content.ReadAsStringAsync();
        return (responseAsString, new(httpRequestMessage, httpResponseMessage));
      }
      catch (Exception ex) {
        return (null, new(httpRequestMessage, ex)); ;
      }
    }

    public static async Task<(string? ResponseAsString, HttpResponse httpResponse)> PostToStringAsync(this HttpClient httpClient, Uri requestUri, HttpContent postBuffer, CancellationTokenSource? cts = null) {
      var httpRequestMessage = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = requestUri, Content = postBuffer };
      try {
        var cancellationToken = cts?.Token ?? default;
        using var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage, cancellationToken);
        if (!httpResponseMessage.IsSuccessStatusCode) return (null, new(httpRequestMessage, httpResponseMessage));

        var responseAsString = await httpResponseMessage.Content.ReadAsStringAsync();
        return (responseAsString, new(httpRequestMessage, httpResponseMessage));
      }
      catch (Exception ex) {
        return (null, new(httpRequestMessage, ex));
      }
    }

  }
}

namespace System {
  public static class ExtensionMethods {
    public static string FullMessage(this Exception ex) {
      if (ex is AggregateException aex) return aex.InnerExceptions.Aggregate("[ ", (total, next) => $"{total}[{next.FullMessage()}] ") + "]";
      var msg = ex.Message.Replace(", see inner exception.", "").Trim();
      var innerMsg = ex.InnerException?.FullMessage();
      if (innerMsg is object && innerMsg!=msg) msg = $"{msg} [ {innerMsg} ]";
      return msg;
    }
  }
}

使用方法:

// download to file
var lastPercent = 0;
bool progressCallback(long bytesRecieved, int percent, float speedKbSec) {
  if (percent > lastPercent) {
    lastPercent = percent;
    Log($"Downloading... {percent}% {speedKbSec/1024.0:0.00}Mbps");
  }
  return true;
}

var (success, httpResponse) = await httpClient.DownloadFileAsync(
  new(myUrlString), 
  localFileName, 
  null, // CancellationTokenSource 
  progressCallback
);

if (success) {
  // file downloaded to localFile, httpResponse.ResponseMessageInfo contain 
  // extra information ie headers and status code

} else {
  Log(httpResponse.ToString()); // human friendly error information
  // if httpResponse.ResponseMessageInfo is object then server refused the request - 
  // examine httpResponse.ResponseMessageInfo.HttpStatusCode etc
  // else we had a Http exception - examine httpResponse.ExceptionInfo 
}


// Http get
var (responseAsString, httpResponse) = await httpClient.GetToStringAsync(url);
if (responseAsString is object) {
  // responseAsString contains the string response from the server

} else {
  // as for DownloadFileAsync
}


// http post
var postBuffer = new StringContent(jsonInString, System.Text.Encoding.UTF8, "application/x-www-form-urlencoded");
var (responseAsString, httpResponse) = await httpClient.PostToStringAsync(url, postBuffer);

if (responseAsString is object) {
  // responseAsString contains the string response from the server

} else {
  Log(httpResponse.ToString()); // human friendly error informaiton
  // as for DownloadFileAsync
}

这是我的解决方案,效果很好:

public static void DownloadFile(string url, string pathToSaveFile)
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            // or: ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

            using (WebDownload client = new WebDownload())
            {
                client.Headers["User-Agent"] = "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36";
                client.DownloadFile(new Uri(url), pathToSaveFile);
            }
        }
    
    public class WebDownload : WebClient
        {
            protected override WebRequest GetWebRequest(Uri address)
            {
                HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
                if (request != null)
                {
                    request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
                }
                return request;
            }
        }

有很多答案,但这是我最近使用的。net 6或更高版本的解决方案。

using var httpClient = new HttpClient();

var tempPath = Path.GetTempFileName();

await using var s = await HttpClient.GetStreamAsync(pdfFilePath);

await using var fs = File.OpenWrite(tempPath);

await s.CopyToAsync(fs);

包括这个命名空间

using System.Net;

异步下载,并在UI线程本身中放置一个ProgressBar来显示下载的状态

private void BtnDownload_Click(object sender, RoutedEventArgs e)
{
    using (WebClient wc = new WebClient())
    {
        wc.DownloadProgressChanged += wc_DownloadProgressChanged;
        wc.DownloadFileAsync (
            // Param1 = Link of file
            new System.Uri("http://www.sayka.com/downloads/front_view.jpg"),
            // Param2 = Path to save
            "D:\\Images\\front_view.jpg"
        );
    }
}
// Event to track the progress
void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    progressBar.Value = e.ProgressPercentage;
}

WebRequest, WebClient和ServicePoint在。net 6(源代码- 2021年11月)中已经过时。

使用System.Net.Http.HttpClient类代替:

using (var client = new HttpClient())
{
    using (var s = client.GetStreamAsync("https://via.placeholder.com/150"))
    {
        using (var fs = new FileStream("localfile.jpg", FileMode.OpenOrCreate))
        {
            s.Result.CopyTo(fs);
        }
    }
}

相同代码的异步版本:

using var client = new HttpClient();
using var s = await client.GetStreamAsync("https://via.placeholder.com/150");
using var fs = new FileStream("localfile.jpg", FileMode.OpenOrCreate);
await s.CopyToAsync(fs);