【问题标题】:Download file with WebClient or HttpClient?使用 WebClient 或 HttpClient 下载文件?
【发布时间】:2017-08-16 10:44:30
【问题描述】:

我正在尝试从 URL 下载文件,我必须在 WebClient 和 HttpClient 之间进行选择。我参考了this 文章和互联网上的其他几篇文章。由于其出色的异步支持和其他 .Net 4.5 特权,建议在任何地方使用 HttpClient。但我仍然不完全相信,需要更多的投入。

我正在使用以下代码从互联网下载文件:

网络客户端:

WebClient client = new WebClient();
client.DownloadFile(downloadUrl, filePath);

HttpClient:

using (HttpClient client = new HttpClient())
{        
    using (HttpResponseMessage response = await client.GetAsync(url))
    using (Stream streamToReadFrom = await response.Content.ReadAsStreamAsync())
    {
    }
}

从我的角度来看,我只能看到使用 WebClient 的一个缺点,那就是非异步调用,阻塞了调用线程。但是如果我不担心线程阻塞或使用client.DownloadFileAsync() 来利用异步支持怎么办?

另一方面,如果我使用 HttpClient,我不是将文件的每个字节都加载到内存中,然后将其写入本地文件吗?如果文件太大,内存开销会不会很贵?如果我们使用 WebClient 可以避免这种情况,因为它会直接写入本地文件而不消耗系统内存。

那么,如果性能是我的首要任务,我应该使用哪种方法进行下载?如果我的上述假设是错误的,我想澄清一下,我也愿意接受其他方法。

【问题讨论】:

标签: c# .net rest httpclient webclient


【解决方案1】:

您可以使用 .Net 4.5+ 本地执行此操作。我尝试按照您的方式进行操作,然后在 Intellisense 中找到了一种似乎有意义的方法。

https://docs.microsoft.com/en-us/dotnet/api/system.io.stream.copytoasync?view=netframework-4.7.2

uri = new Uri(generatePdfsRetrieveUrl + pdfGuid + ".pdf");
HttpClient client = new HttpClient();
var response = await client.GetAsync(uri);
using (var fs = new FileStream(
    HostingEnvironment.MapPath(string.Format("~/Downloads/{0}.pdf", pdfGuid)), 
    FileMode.CreateNew))
{
    await response.Content.CopyToAsync(fs);
}

【讨论】:

  • 我最终使用了这个,注意HostingEnvironment.MapPath(string.Format("~/Downloads/{0}.pdf", pdfGuid)),可以替换为任意路径。
  • HttpClient client = new HttpClient();var response = await client.GetAsync(uri); 不应该在 using 语句中吗?它说他们继承了 IDisposable。
  • @Trisped 可以,但是根据您的使用情况,您应该考虑到当客户端被释放时,端口将处于TIME_WAIT 状态。见here
  • C# 8.0 不会在一次性对象超出范围时自动处理它们吗?
  • @Bluebaron 不,你必须使用using (xxx) { ... }using var xxx = ...; 在范围结束后处理它。
【解决方案2】:

这是我的方法。

如果您正在调用 WebApi 来获取文件,那么您可以从控制器方法中使用 HttpClient GET 请求并使用 FileStreamResult 返回类型返回文件流。

public async Task<ActionResult> GetAttachment(int FileID)
{
    UriBuilder uriBuilder = new UriBuilder();
    uriBuilder.Scheme = "https";
    uriBuilder.Host = "api.example.com";

    var Path = "/files/download";
    uriBuilder.Path = Path;
    using (HttpClient client = new HttpClient())
    {
        client.BaseAddress = new Uri(uriBuilder.ToString());
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Add("authorization", access_token); //if any
        client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
        HttpResponseMessage response = await client.GetAsync(uriBuilder.ToString());

            if (response.IsSuccessStatusCode)
            {
                System.Net.Http.HttpContent content = response.Content;
                var contentStream = await content.ReadAsStreamAsync(); // get the actual content stream
                return File(contentStream, content_type, filename);
            }
            else
            {
                throw new FileNotFoundException();
            }
    }
}

【讨论】:

  • 您不应该将 HttpClient 用作 Disposable 对象,但如果您有很多请求,您会遇到套接字耗尽的情况。改用httpClient有一个静态实例,网上很多文章都涉及到这个问题。
  • 它也不应该用作static实例@RuiLima,而是通过HttpClientFactory进行管理。静态实例也可能导致问题,因为它从不刷新。
【解决方案3】:

为了在使用 WebClient 的现有代码上使用 HttpClient,我编写了一个小的扩展方法来使用它,就像在我的代码中使用 DownloadFileTaskAsync 一样。

using (var client = new System.Net.Http.HttpClient()) // WebClient
{
    var fileName = @"C:\temp\imgd.jpg";
    var uri = new Uri("https://yourwebsite.com/assets/banners/Default.jpg");

    await client.DownloadFileTaskAsync(uri, fileName);
}

要使用它,我们可以有这个扩展方法:

public static class HttpClientUtils
{
    public static async Task DownloadFileTaskAsync(this HttpClient client, Uri uri, string FileName)
    {
        using (var s = await client.GetStreamAsync(uri))
        {
            using (var fs = new FileStream(FileName, FileMode.CreateNew))
            {
                await s.CopyToAsync(fs);
            }
        }
    }
}

【讨论】:

    【解决方案4】:

    Here’s one way to use it to download a URL and save it to a file:(我使用的是 Windows 7,因此没有可用的 WindowsRT,所以我也在使用 System.IO。)

    public static class WebUtils
    {
        private static Lazy<IWebProxy> proxy = new Lazy<IWebProxy>(() => string.IsNullOrEmpty(Settings.Default.WebProxyAddress) ? null : new WebProxy { Address = new Uri(Settings.Default.WebProxyAddress), UseDefaultCredentials = true });
    
        public static IWebProxy Proxy
        {
            get { return WebUtils.proxy.Value; }
        }
    
        public static Task DownloadAsync(string requestUri, string filename)
        {
            if (requestUri == null)
                throw new ArgumentNullException(“requestUri”);
    
            return DownloadAsync(new Uri(requestUri), filename);
        }
    
        public static async Task DownloadAsync(Uri requestUri, string filename)
        {
            if (filename == null)
                throw new ArgumentNullException("filename");
    
            if (Proxy != null)
                WebRequest.DefaultWebProxy = Proxy;
    
            using (var httpClient = new HttpClient())
            {
                using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
                {
                    using (Stream contentStream = await (await httpClient.SendAsync(request)).Content.ReadAsStreamAsync(), stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, Constants.LargeBufferSize, true))
                    {
                        await contentStream.CopyToAsync(stream);
                    }
                }
            }
        }
    } 
    

    请注意,代码将我(在工作中)使用的代理服务器的地址保存在设置中,如果指定了此类设置,则使用该地址。否则,它应该会告诉您有关使用 HttpClient beta 下载和保存文件的所有信息。

    【讨论】:

    【解决方案5】:

    对于重复调用的代码,您希望将HttpClient 放入using 块中(it will leave hanging ports open

    对于使用 HttpClient 下载文件,我发现 this extension method 对我来说似乎是一个不错且可靠的解决方案:

    public static class HttpContentExtensions
    {
        public static Task ReadAsFileAsync(this HttpContent content, string filename, bool overwrite)
        {
            string pathname = Path.GetFullPath(filename);
            if (!overwrite && File.Exists(filename))
            {
                throw new InvalidOperationException(string.Format("File {0} already exists.", pathname));
            }
    
            FileStream fileStream = null;
            try
            {
                fileStream = new FileStream(pathname, FileMode.Create, FileAccess.Write, FileShare.None);
                return content.CopyToAsync(fileStream).ContinueWith(
                    (copyTask) =>
                    {
                        fileStream.Close();
                    });
            }
            catch
            {
                if (fileStream != null)
                {
                    fileStream.Close();
                }
    
                throw;
            }
        }
    }
    

    【讨论】:

      【解决方案6】:

      如果您希望(或必须)同步执行此操作,但使用漂亮的 HttpClient 类,那么有这个简单的方法:

      string requestString = @"https://example.com/path/file.pdf";
      
      var GetTask = httpClient.GetAsync(requestString);
      GetTask.Wait(WebCommsTimeout); // WebCommsTimeout is in milliseconds
      
      if (!GetTask.Result.IsSuccessStatusCode)
      {
          // write an error
          return;
      }
                          
      using (var fs = new FileStream(@"c:\path\file.pdf", FileMode.CreateNew))
      {
          var ResponseTask = GetTask.Result.Content.CopyToAsync(fs);
          ResponseTask.Wait(WebCommsTimeout);
      }
      

      【讨论】:

        【解决方案7】:

        我的方法很简单。使用FileStream,您可以将其存储在本地文件夹中,或者使用FileStreamResult 从API 返回。 存储到本地文件夹的示例:

        private async Task SaveDataIntoLocalFolder(string url,string fileName)
        {
            using (var client = new HttpClient())
            {
                var response = await client.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                   var stream = await response.Content.ReadAsStreamAsync();
                   var fileInfo = new FileInfo(fileName);
                   using (var fileStream = fileInfo.OpenWrite())
                   {
                      await stream.CopyToAsync(fileStream);
                   }
                }
                else
                {
                  throw new Exception("File not found");
                }
            }     
        }
        

        【讨论】:

        • if (response.IsSuccessStatusCode) 行可能会导致成功完成的任务没有完成应做的工作。 response.EnsureSuccessStatusCode() 应该更可取。此外,HttpClient 类旨在实例化 once,并在应用程序的整个生命周期中重复使用。
        • @TheodorZoulias,是的,你是对的。但是 response.EnsureSuccessStatusCode() 会为不成功的操作抛出异常。 HttpClient 可以有多种使用方式,这只是一个演示代码。感谢您的评论。
        【解决方案8】:
           HttpClient _client=new HttpClient();
           byte[] buffer = null;
           try
           {       
              HttpResponseMessage task = await _client.GetAsync("https://**FILE_URL**");
              Stream task2 = await task.Content.ReadAsStreamAsync();
              using (MemoryStream ms = new MemoryStream())
              {
                await task2.CopyToAsync(ms);
                buffer = ms.ToArray();
              }
              File.WriteAllBytes("C:/**PATH_TO_SAVE**", buffer);  
           }
           catch
           {
        
           }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-06-20
          • 2015-12-30
          • 1970-01-01
          • 1970-01-01
          • 2011-07-14
          • 2017-05-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多