C# скачать большой файл с сервера с меньшим потреблением памяти



У меня есть большой файл с объемом памяти 42 Мб. Я хочу загрузить файл с меньшим потреблением памяти.

Код Контроллера



public ActionResult Download()
{
var filePath = "file path in server";
FileInfo file = new FileInfo(filePath);
Response.ContentType = "application/zip";
Response.AppendHeader("Content-Disposition", "attachment; filename=folder.zip");
Response.TransmitFile(file.FullName);
Response.End();
}


Alernative метод, опробованный с потоком



public ActionResult Download()
{
string failure = string.Empty;
Stream stream = null;
int bytesToRead = 10000;


long LengthToRead;
try
{
var path = "file path from server";
FileWebRequest fileRequest = (FileWebRequest)FileWebRequest.Create(path);
FileWebResponse fileResponse = (FileWebResponse)fileRequest.GetResponse();

if (fileRequest.ContentLength > 0)
fileResponse.ContentLength = fileRequest.ContentLength;

//Get the Stream returned from the response
stream = fileResponse.GetResponseStream();

LengthToRead = stream.Length;

//Indicate the type of data being sent
Response.ContentType = "application/octet-stream";

//Name the file
Response.AddHeader("Content-Disposition", "attachment; filename=SolutionWizardDesktopClient.zip");
Response.AddHeader("Content-Length", fileResponse.ContentLength.ToString());

int length;
do
{
// Verify that the client is connected.
if (Response.IsClientConnected)
{
byte[] buffer = new Byte[bytesToRead];

// Read data into the buffer.
length = stream.Read(buffer, 0, bytesToRead);

// and write it out to the response's output stream
Response.OutputStream.Write(buffer, 0, length);

// Flush the data
Response.Flush();

//Clear the buffer
LengthToRead = LengthToRead - length;
}
else
{
// cancel the download if client has disconnected
LengthToRead = -1;
}
} while (LengthToRead > 0); //Repeat until no data is read

}
finally
{
if (stream != null)
{
//Close the input stream
stream.Close();
}
Response.End();
Response.Close();
}
return View("Failed");
}


Из-за размера файла, он потребляет больше памяти, что приводит к проблеме производительности.

После проверки в журнале iis процесс загрузки занимает 42 МБ и 64 МБ соответственно.

Заранее спасибо

854   5  

5 ответов:

Лучшим вариантом было бы использовать FileResult вместо ActionResult:

Использование этого метода означает, что вам не нужно загружать файл/байты в память перед подачей.

public FileResult Download()
{
     var filePath = "file path in server";
     return new FilePathResult(Server.MapPath(filePath), "application/zip");
}

Edit: для больших файлов FilePathResult также завершится ошибкой.

Ваш лучший выбор, вероятно, ответ.Тогда TransmitFile (). Я использовал это на больших файлах (GBs) и не имел проблем раньше

public ActionResult Download()
{

    var filePath = @"file path from server";

    Response.Clear();
    Response.ContentType = "application/octet-stream";
    Response.AppendHeader("Content-Disposition", "filename=" + filePath);

    Response.TransmitFile(filePath);

    Response.End();

    return Index();
}

Из MSDN:

Записывает указанный файл непосредственно в выходной поток HTTP-ответа, без буферизации в памяти.

Попробуйте установить заголовок Transfer-Encoding в chunked и вернуть HttpResponseMessage с помощью PushStreamContent. Передача-кодирование фрагментов означает, что HTTP-ответ не будет иметь заголовка длины содержимого, и поэтому клиент должен будет анализировать фрагменты HTTP-ответа как поток. Заметьте, я никогда не сталкивался с клиентом (браузером и т. д.), который не обрабатывал кодировку передачи. Вы можете прочитать больше по ссылке под.

Https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding

    [HttpGet]
    public async Task<HttpResponseMessage> Download(CancellationToken token)
    {
        var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
        {
            Content = new PushStreamContent(async (stream, context, transportContext) =>
            {
                try
                {
                    using (var fileStream = System.IO.File.OpenRead("some path to MyBigDownload.zip"))
                    {
                        await fileStream.CopyToAsync(stream);
                    }
                }
                finally
                {
                    stream.Close();
                }
            }, "application/octet-stream"),
        };
        response.Headers.TransferEncodingChunked = true;
        response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
        {
            FileName = "MyBigDownload.zip"
        };
        return response;
    }

Вам просто нужно использовать IIS, чтобы включить загрузку HTTP посмотрите на эту ссылку

И вам просто нужно вернуть HTTP-путь файла, который будет загружаться так быстро и так легко.

У меня была похожая проблема, но у меня не было файла на локальном диске, я должен был загрузить его из API (мой MVC был как прокси). Главное-установить Response.Buffer=false; на вашем действии MVC. Я думаю, что первое решение @JanusPienaar должно работать с этим. Мое действие MVC:

public class HomeController : Controller
{
    public async Task<FileStreamResult> Streaming(long RecordCount)
    {
        HttpClient Client;
        System.IO.Stream Stream;

        //This is the key thing
        Response.Buffer=false;

        Client = new HttpClient() { BaseAddress=new Uri("http://MyApi", };
        Stream = await Client.GetStreamAsync("api/Streaming?RecordCount="+RecordCount);
        return new FileStreamResult(Stream, "text/csv");
    }
}

И мой тест WebApi (который генерирует файл):

public class StreamingController : ApiController
{
    // GET: api/Streaming/5
    public HttpResponseMessage Get(long RecordCount)
    {
        var response = Request.CreateResponse();

        response.Content=new PushStreamContent((stream, http, transport) =>
        {
            RecordsGenerator Generator = new RecordsGenerator();
            long i;

            using(var writer = new System.IO.StreamWriter(stream, System.Text.Encoding.UTF8))
            {
                for(i=0; i<RecordCount; i++)
                {
                    writer.Write(Generator.GetRecordString(i));

                    if(0==(i&0xFFFFF))
                        System.Diagnostics.Debug.WriteLine($"Record no: {i:N0}");
                    }
                }
            });

            return response;
        }

        class RecordsGenerator
        {
            const string abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            char[] Chars = new char[14];//Ceiling(log26(2^63))

            public string GetRecordString(long Record)
            {
                int iLength = 0;
                long Div = Record, Mod;

                do
                {
                    iLength++;
                    Div=Math.DivRem(Div, abc.Length, out Mod);
                    //Save from backwards
                    Chars[Chars.Length-iLength]=abc[(int)Mod];
                }
                while(Div!=0);

                return $"{Record} {new string(Chars, Chars.Length-iLength, iLength)}\r\n";
            }
        }
    }
}

Если RecordCount равен 100000000, то файл, сгенерированный TestApi, равен 1,56 ГБ. Ни WebApi, ни MVC не потребляют столько памяти.

Есть сообщение Ризвана Ансари , которое работало для меня:

Есть ситуации, когда вам нужно предоставить опцию загрузки для большого файла, расположенного где-то на сервере или созданного во время выполнения. Ниже функция может быть использована для загрузки файлов любого размера. Иногда загрузка большого файла вызывает исключение OutOfMemoryException, показывающее "недостаточно памяти для продолжения выполнения программы". Поэтому эта функция также обрабатывает эту ситуацию, разбивая файл на куски размером 1 МБ (можно настроить, изменив переменную bufferSize).

Использование:

DownloadLargeFile("A big file.pdf", "D:\\Big Files\\Big File.pdf", "application/pdf", System.Web.HttpContext.Current.Response);

Вы можете изменить "application / pdf" на правильный Тип Mime

Функция Загрузки:

public static void DownloadLargeFile(string DownloadFileName, string FilePath, string ContentType, HttpResponse response)
    {
        Stream stream = null;

        // read buffer in 1 MB chunks
        // change this if you want a different buffer size
        int bufferSize = 1048576;

        byte[] buffer = new Byte[bufferSize];

        // buffer read length
        int length;
        // Total length of file
        long lengthToRead;

        try
        {
            // Open the file in read only mode 
            stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);

            // Total length of file
            lengthToRead = stream.Length;
            response.ContentType = ContentType;
            response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(DownloadFileName, System.Text.Encoding.UTF8));

            while (lengthToRead > 0)
            {
                // Verify that the client is connected.
                if (response.IsClientConnected)
                {
                    // Read the data in buffer
                    length = stream.Read(buffer, 0, bufferSize);

                    // Write the data to output stream.
                    response.OutputStream.Write(buffer, 0, length);

                    // Flush the data 
                    response.Flush();

                    //buffer = new Byte[10000];
                    lengthToRead = lengthToRead - length;
                }
                else
                {
                    // if user disconnects stop the loop
                    lengthToRead = -1;
                }
            }
        }
        catch (Exception exp)
        {
            // handle exception
            response.ContentType = "text/html";
            response.Write("Error : " + exp.Message);
        }
        finally
        {
            if (stream != null)
            {
                stream.Close();
            }
            response.End();
            response.Close();
        }
    }

Comments

    Ничего не найдено.