用 WebClient 还是 HttpClient 下载文件?

我试图从一个 URL 下载文件,我必须在 WebClient 和 HttpClient 之间做出选择。我已经在互联网上参考了 这个的文章和其他几篇文章。在任何地方,都建议使用 HttpClient,因为它有很强的异步支持和其他支持。净4.5特权。但我仍然不完全信服,需要更多的投入。

我使用以下代码从网上下载文件:

网上客户端:

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,这是可以避免的,因为它将直接写入本地文件,而不会占用系统内存。

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

155247 次浏览

Here’s one way to use it to download a URL and save it to a file: (I am using windows 7, therefore no WindowsRT available to me, so I’m also using 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);
}
}
}
}
}

Note that code is saving the address of the proxy server I use (at work) in a setting, and using that if such setting is specified. Otherwise, it should tell you all you need to know regarding using the HttpClient beta to download and save a file.

Here is my approach.

If you are calling a WebApi to get a file, then from a controller method you can use HttpClient GET request and return file stream using FileStreamResult return type.

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();
}
}
}

For code being called repeatedly, you do not want to put HttpClient in a using block (it will leave hanging ports open)

For downloading a file with HttpClient, I found this extension method which seemed like a good and reliable solution to me:

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;
}
}
}

You can do it natively with .Net 4.5+. I tried doing it your way and then I just found a method in Intellisense that seemed to make sense.

https://learn.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);
}

If you want (or have) to do this synchronously, but using the nice HttpClient class, then there's this simple approach:

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);
}
   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
{


}

To use HttpClient on my existing code that used WebClient, I wrote a small extension method to use it on the same way I used DownloadFileTaskAsync on my code.

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);
}

To use it we can have this extension method:

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);
}
}
}
}

My approach is very simple. Using FileStream you can store it in the local folder, or return it from API using FileStreamResult. Example for store into local folder:

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");
}
}
}