5

This is my code:

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

However, DownloadData(zipPath) gives error as WebException: "The message length limit was exceeded" it seems like it can't read more than 2GB of size, I searched and it needs to edit some property to -1 of an object which i didn't use in my code.. https://social.msdn.microsoft.com/Forums/en-US/88d0c0bb-ec86-435d-9d2a-c5ec821e9a79/httpwebresponse-maximum-response-header-size-response-over-64k-problem?forum=winappswithcsharp

and I have tried this aswell:

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

Gave this exception: System.IO.IOException: 'The file is too long. This operation is currently limited to supporting files less than 2 gigabytes in size.'

I don't know what to do anymore.. any advice? In the end i'm trying to return File(byte[]) so I could download that file from the server.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • Check this [uploading-and-downloading-large-files-in-asp-net-core-3-1](https://stackoverflow.com/questions/62502286/uploading-and-downloading-large-files-in-asp-net-core-3-1) – Genusatplay Aug 02 '21 at 17:57
  • @Genusatplay thank you, Unfortunately I looked into it already, No helpful code to be found there, just some words here and there + he even didn't finish the so called method he created as example of .net core ... –  Aug 02 '21 at 22:43
  • There is a good explanation of download restrictions in this post, you can refer to the following which may be helpful to you.https://stackoverflow.com/questions/62502286/uploading-and-downloading-large-files-in-asp-net-core-3-1 – Tupac Aug 03 '21 at 08:33
  • @Chaodeng Thank you, I have looked into all github posts already, And this one again says what to do and not explaining nor giving reference of how to use the information given, and I have tried my own research but couldn't find any possible fix, So If you could do a code sample of what you think is working then feel free to go! –  Aug 03 '21 at 08:37
  • @AhmedAyman `public async Task GetFile() { ... return File(stream, "application/octet-stream"); }` You try this too? – Genusatplay Aug 07 '21 at 21:40
  • @Genusatplay Yeah I tried that and it requires workaround #1 in my solution because still stream is going to be limited in .NET . –  Aug 08 '21 at 22:29

2 Answers2

7

Thanks for everyone who tried helping me, I have found the solution myself after digging hard in the documentation and re checking answers, combining ideas, etc.. You got two solutions:

1- Make a class that implements Stream as a HugeMemoryStream, Which of course you can find here:

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- or just push the file to the clients by simply returning the file from HDD (this might be slow if you want faster transfer use better storage units or move them to the blazing fast RAM..) through the controller and to the view. using this specific return type:

return new PhysicalFileResult("Directory Containing File", 
"application/octet-stream") 
{ FileDownloadName = "Your file name + extension, for example: test.txt or test.zip etc.." };

It was pain in the ass but worth it since no one is really answering this question online :)

1

Trying to return items > 2GB from the same I/O call is never gonna end well. At no point ever do you want a single byte[] with then entire contents of the item. Instead, when working with such large items you want to get a stream, where you can read and buffer only small segments at a time. You can use WebClient.GetWebRequest() and WebClient.GetWebResponse() to accomplish this.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • 3
    Can you like show small example or edit code and show a sample of a download IActionResult method (Controller) so I could understand it's usage more ? –  Aug 02 '21 at 21:54