Загрузить файл с помощью WebClient или HttpClient?

Я пытаюсь загрузить файл с URL-адреса, и мне нужно выбрать между WebClient и HttpClient. Я ссылался на эту статью и несколько других статей в Интернете. Всюду предлагается перейти на HttpClient из-за его большой поддержки async и других привилегий.Net 4.5. Но я все еще не полностью убежден и нуждаюсь в большем количестве ресурсов.

Я использую ниже код для загрузки файла из Интернета:

WebClient:

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() чтобы использовать поддержку async?

С другой стороны, если я использую HttpClient, я не загружаю каждый байт файла в память, а затем записываю его в локальный файл? Если размер файла слишком велик, не будет ли дорогостоящим издержки памяти? Который можно было бы избежать, если мы будем использовать WebClient, поскольку он будет напрямую записывать в локальный файл и не потреблять системную память.

Итак, если производительность - мой максимальный приоритет, какой подход я должен использовать для загрузки? Я хотел бы получить разъяснение, если мое предположение неверно, и я также открыт для альтернативного подхода.

Ответ 1

Вот мой подход.

Если вы вызываете WebApi для получения файла, то из метода контроллера вы можете использовать запрос GETClient 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();
            }
    }
}

Ответ 2

Вот один из способов использовать его для загрузки URL-адреса и сохранения его в файл: (Я использую 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 для загрузки и сохранения файла.

Ответ 3

Вы можете сделать это изначально с .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);
}

Ответ 4

Для многократного HttpClient кода вы не хотите помещать HttpClient в блок using (он будет оставлять висячие порты открытыми)

Для загрузки файла с HttpClient я нашел этот метод расширения, который казался хорошим и надежным решением для меня:

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

Ответ 5

Если вы хотите (или должны) делать это синхронно, но с использованием классного класса 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);
                }