在 ASP.NET 核心中实现下载 > 2gb 的大文件
Implement Downloading Large files > 2gb in ASP.NET Core
这是我的代码:
var net = new System.Net.WebClient();
var data = net.DownloadData(zipPath);
var content = new MemoryStream(data);
var contentType = "APPLICATION/octet-stream";
var fileName = zipPath.Split('\')[zipPath.Split('\').Length -1];
Response.Cookies.Append("download", "finished");
return File(content, contentType, fileName);
但是,DownloadData(zipPath) 给出了 WebException 错误:“超出了消息长度限制”
似乎它不能读取超过 2GB 的大小,我搜索了一下,它需要编辑一些 属性 到 -1 的对象,我没有在我的代码中使用它.. https://social.msdn.microsoft.com/Forums/en-US/88d0c0bb-ec86-435d-9d2a-c5ec821e9a79/httpwebresponse-maximum-response-header-size-response-over-64k-problem?forum=winappswithcsharp
我也试过了:
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(zipPath, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
给出了这个例外:System.IO.IOException:'The file is too long. This operation is currently limited to supporting files less than 2 gigabytes in size.'
我不知道该怎么办了..有什么建议吗?
最后,我尝试 return File(byte[]) 以便我可以从服务器下载该文件。
尝试从同一个 I/O 调用 return 项目 > 2GB 永远不会有好结果。在任何时候,您都不需要单个 byte[]
来包含项目的全部内容。相反,在处理如此大的项目时,您希望获得 stream,您一次只能读取和缓冲小段。您可以使用 WebClient.GetWebRequest()
和 WebClient.GetWebResponse()
来完成此操作。
感谢所有尝试帮助我的人,在努力挖掘文档并重新检查答案、结合想法等之后,我自己找到了解决方案。
您有两个解决方案:
1- 制作一个 class 将 Stream 实现为 HugeMemoryStream,当然你可以在这里找到它:
class HugeMemoryStream : System.IO.Stream
{
#region Fields
private const int PAGE_SIZE = 1024000000;
private const int ALLOC_STEP = 1024;
private byte[][] _streamBuffers;
private int _pageCount = 0;
private long _allocatedBytes = 0;
private long _position = 0;
private long _length = 0;
#endregion Fields
#region Internals
private int GetPageCount(long length)
{
int pageCount = (int)(length / PAGE_SIZE) + 1;
if ((length % PAGE_SIZE) == 0)
pageCount--;
return pageCount;
}
private void ExtendPages()
{
if (_streamBuffers == null)
{
_streamBuffers = new byte[ALLOC_STEP][];
}
else
{
byte[][] streamBuffers = new byte[_streamBuffers.Length + ALLOC_STEP][];
Array.Copy(_streamBuffers, streamBuffers, _streamBuffers.Length);
_streamBuffers = streamBuffers;
}
_pageCount = _streamBuffers.Length;
}
private void AllocSpaceIfNeeded(long value)
{
if (value < 0)
throw new InvalidOperationException("AllocSpaceIfNeeded < 0");
if (value == 0)
return;
int currentPageCount = GetPageCount(_allocatedBytes);
int neededPageCount = GetPageCount(value);
while (currentPageCount < neededPageCount)
{
if (currentPageCount == _pageCount)
ExtendPages();
_streamBuffers[currentPageCount++] = new byte[PAGE_SIZE];
}
_allocatedBytes = (long)currentPageCount * PAGE_SIZE;
value = Math.Max(value, _length);
if (_position > (_length = value))
_position = _length;
}
#endregion Internals
#region Stream
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => true;
public override long Length => _length;
public override long Position
{
get { return _position; }
set
{
if (value > _length)
throw new InvalidOperationException("Position > Length");
else if (value < 0)
throw new InvalidOperationException("Position < 0");
else
_position = value;
}
}
public override void Flush() { }
public override int Read(byte[] buffer, int offset, int count)
{
int currentPage = (int)(_position / PAGE_SIZE);
int currentOffset = (int)(_position % PAGE_SIZE);
int currentLength = PAGE_SIZE - currentOffset;
long startPosition = _position;
if (startPosition + count > _length)
count = (int)(_length - startPosition);
while (count != 0 && _position < _length)
{
if (currentLength > count)
currentLength = count;
Array.Copy(_streamBuffers[currentPage++], currentOffset, buffer, offset, currentLength);
offset += currentLength;
_position += currentLength;
count -= currentLength;
currentOffset = 0;
currentLength = PAGE_SIZE;
}
return (int)(_position - startPosition);
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
break;
case SeekOrigin.Current:
offset += _position;
break;
case SeekOrigin.End:
offset = _length - offset;
break;
default:
throw new ArgumentOutOfRangeException("origin");
}
return Position = offset;
}
public override void SetLength(long value)
{
if (value < 0)
throw new InvalidOperationException("SetLength < 0");
if (value == 0)
{
_streamBuffers = null;
_allocatedBytes = _position = _length = 0;
_pageCount = 0;
return;
}
int currentPageCount = GetPageCount(_allocatedBytes);
int neededPageCount = GetPageCount(value);
// Removes unused buffers if decreasing stream length
while (currentPageCount > neededPageCount)
_streamBuffers[--currentPageCount] = null;
AllocSpaceIfNeeded(value);
if (_position > (_length = value))
_position = _length;
}
public override void Write(byte[] buffer, int offset, int count)
{
int currentPage = (int)(_position / PAGE_SIZE);
int currentOffset = (int)(_position % PAGE_SIZE);
int currentLength = PAGE_SIZE - currentOffset;
long startPosition = _position;
AllocSpaceIfNeeded(_position + count);
while (count != 0)
{
if (currentLength > count)
currentLength = count;
Array.Copy(buffer, offset, _streamBuffers[currentPage++], currentOffset, currentLength);
offset += currentLength;
_position += currentLength;
count -= currentLength;
currentOffset = 0;
currentLength = PAGE_SIZE;
}
}
#endregion Stream
}
2- 或仅通过 return 将文件从 HDD 推送到客户端(如果您想要更快的传输使用更好的存储单元或将它们移动到超快的 RAM,这可能会很慢。 .) 通过控制器和视图。
使用这个特定的 return 类型:
return new PhysicalFileResult("Directory Containing File",
"application/octet-stream")
{ FileDownloadName = "Your file name + extension, for example: test.txt or test.zip etc.." };
这很痛苦,但值得,因为没有人真正在线回答这个问题:)
这是我的代码:
var net = new System.Net.WebClient();
var data = net.DownloadData(zipPath);
var content = new MemoryStream(data);
var contentType = "APPLICATION/octet-stream";
var fileName = zipPath.Split('\')[zipPath.Split('\').Length -1];
Response.Cookies.Append("download", "finished");
return File(content, contentType, fileName);
但是,DownloadData(zipPath) 给出了 WebException 错误:“超出了消息长度限制” 似乎它不能读取超过 2GB 的大小,我搜索了一下,它需要编辑一些 属性 到 -1 的对象,我没有在我的代码中使用它.. https://social.msdn.microsoft.com/Forums/en-US/88d0c0bb-ec86-435d-9d2a-c5ec821e9a79/httpwebresponse-maximum-response-header-size-response-over-64k-problem?forum=winappswithcsharp
我也试过了:
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(zipPath, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
给出了这个例外:System.IO.IOException:'The file is too long. This operation is currently limited to supporting files less than 2 gigabytes in size.'
我不知道该怎么办了..有什么建议吗? 最后,我尝试 return File(byte[]) 以便我可以从服务器下载该文件。
尝试从同一个 I/O 调用 return 项目 > 2GB 永远不会有好结果。在任何时候,您都不需要单个 byte[]
来包含项目的全部内容。相反,在处理如此大的项目时,您希望获得 stream,您一次只能读取和缓冲小段。您可以使用 WebClient.GetWebRequest()
和 WebClient.GetWebResponse()
来完成此操作。
感谢所有尝试帮助我的人,在努力挖掘文档并重新检查答案、结合想法等之后,我自己找到了解决方案。 您有两个解决方案:
1- 制作一个 class 将 Stream 实现为 HugeMemoryStream,当然你可以在这里找到它:
class HugeMemoryStream : System.IO.Stream
{
#region Fields
private const int PAGE_SIZE = 1024000000;
private const int ALLOC_STEP = 1024;
private byte[][] _streamBuffers;
private int _pageCount = 0;
private long _allocatedBytes = 0;
private long _position = 0;
private long _length = 0;
#endregion Fields
#region Internals
private int GetPageCount(long length)
{
int pageCount = (int)(length / PAGE_SIZE) + 1;
if ((length % PAGE_SIZE) == 0)
pageCount--;
return pageCount;
}
private void ExtendPages()
{
if (_streamBuffers == null)
{
_streamBuffers = new byte[ALLOC_STEP][];
}
else
{
byte[][] streamBuffers = new byte[_streamBuffers.Length + ALLOC_STEP][];
Array.Copy(_streamBuffers, streamBuffers, _streamBuffers.Length);
_streamBuffers = streamBuffers;
}
_pageCount = _streamBuffers.Length;
}
private void AllocSpaceIfNeeded(long value)
{
if (value < 0)
throw new InvalidOperationException("AllocSpaceIfNeeded < 0");
if (value == 0)
return;
int currentPageCount = GetPageCount(_allocatedBytes);
int neededPageCount = GetPageCount(value);
while (currentPageCount < neededPageCount)
{
if (currentPageCount == _pageCount)
ExtendPages();
_streamBuffers[currentPageCount++] = new byte[PAGE_SIZE];
}
_allocatedBytes = (long)currentPageCount * PAGE_SIZE;
value = Math.Max(value, _length);
if (_position > (_length = value))
_position = _length;
}
#endregion Internals
#region Stream
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => true;
public override long Length => _length;
public override long Position
{
get { return _position; }
set
{
if (value > _length)
throw new InvalidOperationException("Position > Length");
else if (value < 0)
throw new InvalidOperationException("Position < 0");
else
_position = value;
}
}
public override void Flush() { }
public override int Read(byte[] buffer, int offset, int count)
{
int currentPage = (int)(_position / PAGE_SIZE);
int currentOffset = (int)(_position % PAGE_SIZE);
int currentLength = PAGE_SIZE - currentOffset;
long startPosition = _position;
if (startPosition + count > _length)
count = (int)(_length - startPosition);
while (count != 0 && _position < _length)
{
if (currentLength > count)
currentLength = count;
Array.Copy(_streamBuffers[currentPage++], currentOffset, buffer, offset, currentLength);
offset += currentLength;
_position += currentLength;
count -= currentLength;
currentOffset = 0;
currentLength = PAGE_SIZE;
}
return (int)(_position - startPosition);
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
break;
case SeekOrigin.Current:
offset += _position;
break;
case SeekOrigin.End:
offset = _length - offset;
break;
default:
throw new ArgumentOutOfRangeException("origin");
}
return Position = offset;
}
public override void SetLength(long value)
{
if (value < 0)
throw new InvalidOperationException("SetLength < 0");
if (value == 0)
{
_streamBuffers = null;
_allocatedBytes = _position = _length = 0;
_pageCount = 0;
return;
}
int currentPageCount = GetPageCount(_allocatedBytes);
int neededPageCount = GetPageCount(value);
// Removes unused buffers if decreasing stream length
while (currentPageCount > neededPageCount)
_streamBuffers[--currentPageCount] = null;
AllocSpaceIfNeeded(value);
if (_position > (_length = value))
_position = _length;
}
public override void Write(byte[] buffer, int offset, int count)
{
int currentPage = (int)(_position / PAGE_SIZE);
int currentOffset = (int)(_position % PAGE_SIZE);
int currentLength = PAGE_SIZE - currentOffset;
long startPosition = _position;
AllocSpaceIfNeeded(_position + count);
while (count != 0)
{
if (currentLength > count)
currentLength = count;
Array.Copy(buffer, offset, _streamBuffers[currentPage++], currentOffset, currentLength);
offset += currentLength;
_position += currentLength;
count -= currentLength;
currentOffset = 0;
currentLength = PAGE_SIZE;
}
}
#endregion Stream
}
2- 或仅通过 return 将文件从 HDD 推送到客户端(如果您想要更快的传输使用更好的存储单元或将它们移动到超快的 RAM,这可能会很慢。 .) 通过控制器和视图。 使用这个特定的 return 类型:
return new PhysicalFileResult("Directory Containing File",
"application/octet-stream")
{ FileDownloadName = "Your file name + extension, for example: test.txt or test.zip etc.." };
这很痛苦,但值得,因为没有人真正在线回答这个问题:)