C# 我可以直接从HttpResponseMessage流式传输到文件而不需要经过内存吗?

C# 我可以直接从HttpResponseMessage流式传输到文件而不需要经过内存吗?,c#,stream,asp.net-web-api2,C#,Stream,Asp.net Web Api2,我的程序使用HttpClient向Web API发送GET请求,这将返回一个文件 我现在使用以下代码(简化)将文件存储到光盘: public async Task<bool> DownloadFile() { var client = new HttpClient(); var uri = new Uri("http://somedomain.com/path"); var response = await client.GetAsync(uri);

我的程序使用HttpClient向Web API发送GET请求,这将返回一个文件

我现在使用以下代码(简化)将文件存储到光盘:

public async Task<bool> DownloadFile()
{
    var client = new HttpClient();
    var uri = new Uri("http://somedomain.com/path");
    var response = await client.GetAsync(uri);

    if (response.IsSuccessStatusCode)
    {
        var fileName = response.Content.Headers.ContentDisposition.FileName;
        using (var fs = new FileStream("C:\test\" + fileName, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            await response.Content.CopyToAsync(fs);
            return true;
        }
    }

    return false;
}
公共异步任务下载文件()
{
var client=新的HttpClient();
var uri=新的uri(“http://somedomain.com/path");
var response=await client.GetAsync(uri);
if(响应。IsSuccessStatusCode)
{
var fileName=response.Content.Headers.ContentDisposition.fileName;
使用(var fs=new FileStream(“C:\test\”+文件名,FileMode.Create,FileAccess.Write,FileShare.None))
{
等待响应。Content.CopyToAsync(fs);
返回true;
}
}
返回false;
}
现在,当这段代码运行时,进程会将所有文件加载到内存中。实际上,我更希望流从HttpResponseMessage.Content流到FileStream,这样只有一小部分保存在内存中

我们计划在大文件(>1GB)上使用它,那么有没有一种方法可以在不将所有文件都存储在内存中的情况下实现这一点


理想情况下,无需手动循环将一部分读取到字节[]并将该部分写入文件流,直到所有内容都被写入?

看起来这是出于设计——如果您查看文档中的
HttpClient.GetAsync()
,您会看到它说:

返回的任务对象将在整个响应完成后完成 (包括内容)被读取

您可以改为使用
HttpClient.GetStreamAsync()
,具体说明:

此方法不缓冲流

但是,就我所见,您无法访问响应中的标题。由于这可能是一个要求(因为您要从标题中获取文件名),因此您可能需要使用
HttpWebRequest
,它允许您在不将整个响应读入内存的情况下获取响应详细信息(标题等)。比如:

public async Task<bool> DownloadFile()
{
    var uri = new Uri("http://somedomain.com/path");
    var request = WebRequest.CreateHttp(uri);
    var response = await request.GetResponseAsync();

    ContentDispositionHeaderValue contentDisposition;
    var fileName = ContentDispositionHeaderValue.TryParse(response.Headers["Content-Disposition"], out contentDisposition)
        ? contentDisposition.FileName
        : "noname.dat";
    using (var fs = new FileStream(@"C:\test\" + fileName, FileMode.Create, FileAccess.Write, FileShare.None))
    {
        await response.GetResponseStream().CopyToAsync(fs);
    }

    return true
}
公共异步任务下载文件()
{
var uri=新的uri(“http://somedomain.com/path");
var-request=WebRequest.CreateHttp(uri);
var response=wait request.GetResponseAsync();
contentdispositionheadervaluecontentdisposition;
var fileName=ContentDispositionHeaderValue.TryParse(response.Headers[“Content Disposition”],out contentDisposition)
?contentDisposition.FileName
:“noname.dat”;
使用(var fs=new FileStream(@“C:\test\”+文件名,FileMode.Create,FileAccess.Write,FileShare.None))
{
等待响应。GetResponseStream().CopyToAsync(fs);
}
返回真值
}
请注意,如果请求返回不成功的响应代码,将引发异常,因此您可能希望在本例中使用
try..catch
并返回
false
,与原始示例相同。

而不是
GetAsync(Uri)
使用
GetAsync(Uri,HttpCompletionOption)
使用
httpcompletion选项重载。ResponseHeadersRead

这同样适用于
SendAsync
和其他
HttpClient

资料来源:

  • 文件(见备注)
读取部分或全部响应(包括内容)后,返回的任务对象将根据completionOption参数完成

  • 使用
    HttpCompletionOption.ResponseHeadersRead

  • (不要介意对response的评论,
    ResponseHeadersRead
    是关键所在)

看看-
CopyToAsync
已经在做你描述的事情了(在内部,它重复从响应中读取一块数据并将其写入文件,直到所有数据都被传输),它不应该导致立即将整个文件缓冲到内存中。我也这么认为,但是从内存消耗来看,它确实会将整个文件加载到内存中,这是我想要避免的。您是否尝试过
response.Content.ReadAsStreamAsync()
并使用
Stream.CopyToAsync
?那么可能是您对“内存消耗”的测量不准确。除非需要,否则运行时不一定会释放“空闲”内存,因此任务管理器等中的内存使用值可能不会反映应用程序实际“使用”的内存。感谢您的澄清。实际上,在客户机上使用GetStreamAsync允许将流直接保存到磁盘而不是内存。因为我们也可以控制服务器,所以我可以首先在单独的请求中获取元数据(文件名、大小)。那么我就不需要访问标题了。