Jak w MVC5 pobrać generowany plik tekstowy bez trzymania go całego w pamięci serwera?

2

Kontynuując temat mam kolejny problem do rozwiązania. Obecne rozwiązanie wygląda tak i działa:

        public ActionResult ExportSimpleCsv(int? businessLineId = 0)
        {
            IEnumerable<BusinessLineDataDTO> data = unitOfWork.BusinessLineAggregateDatas.GetDto(businessLineId);

            Response.BufferOutput = false;
            var stream = new MemoryStream();
            var writer = new StreamWriter(stream);

            foreach (var s in data)
            {
                string isRejectedStr = s.IsRejected ? "YES" : "NO";
                writer.WriteLine($"{s.Id};{s.ComplaintNumber};{isRejectedStr};{s.ReasonForRejection}");
            }

            stream.Position = 0;

            return File(stream, "text/csv", $"Line{businessLineId}.csv");
        }

Wołanie akcji z widoku:

@Html.ActionLink("Pobierz CSV", "ExportSimpleCsv", "Export", new { id = Model.Lines[i].Id });

W tym przypadku pod koniec generowania MemoryStream zajmuje ok. 100mb czyli tyle ile plik, który pobieramy. Jest to oczekiwane zachowanie, ale potrzebuję innego rozwiązania.

Domyślam się, że powinienem chyba zamiast z MemoryStream skorzystać z innego typu strumienia, który przekażę klientowi, a następnie asynchronicznie będę do tego strumienia pisał.

Przekopałem już kawał internetu i nie znalazłem rozwiązania.

screenshot-20181226160922.png

0

Nie jestem pewien ale wydaje mi się że w ASP.NET MVC nie da się streamować danych. Dla testu taki kod:

  public async Task<HttpResponseMessage> Get()
  {
     var stream = await Client.GetStreamAsync("jakiś plik");

      var result = new HttpResponseMessage(HttpStatusCode.OK)
      {
           Content = new StreamContent(stream),
      };
      result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
      result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "nazwa" };
      return result;
   }

W kontrolerze MVC (klasa Controller) nie pobierze pliku, ale w kontrolerze WebApi (klasa ApiController) zadziała poprawnie.

0
error91 napisał(a):

W kontrolerze MVC (klasa Controller) nie pobierze pliku, ale w kontrolerze WebApi (klasa ApiController) zadziała poprawnie.

Przetestowałem i potwierdzam.
Pytanie teraz tylko jak to przesłać przez StreamContent. Bo chcę treść sam generować.

Stworzyłem takiego potworka, że to strach pokazywać komukolwiek:

        [HttpGet]
        public async Task<HttpResponseMessage> Get()
        {
            IEnumerable<BusinessLineDataDTO> data = unitOfWork.BusinessLineAggregateDatas.GetDto(1);

            MemoryStream ms = new MemoryStream();
            StreamContent streamContent = new StreamContent(ms);
            var writer = new StreamWriter(ms);

            foreach (var s in data)
            {
                    string isRejectedStr = s.IsRejected ? "YES" : "NO";
                    await writer.WriteLineAsync($"{System.Environment.NewLine}{s.Id};{s.ComplaintNumber};{isRejectedStr};{s.ReasonForRejection}");

                    ms.Position = 0;
                    await streamContent.CopyToAsync(ms);
                    await writer.FlushAsync();
                    await ms.FlushAsync();
            }

            var result = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = streamContent
            };

            result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
            result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "nazwa.csv" };

            return result;
        }``

Kod wywołuje się niesamowicie długo ponieważ flush tak często wywoływany zabiera masę czasu i wygenerowanie pliku trwa dziesięć razy dłużej. Bez flusha natomiast memory stream rośnie w RAMie. Gdy natomiast tworzę memorystream w klauzuli using w pętli to dostaję pusty plik.
1

To widziałeś?

https://stackoverflow.com/questions/43804446/c-sharp-download-big-file-from-server-with-less-memory-consumption

Szczególnie

I had similar problem but I didn't have file on local disk, I had to download it from API (my MVC was like a proxy). The key thing is to set Response.Buffer=false; on your MVC Action. I think @JanusPienaar's first solution should work with this. My MVC action is:

Ewentualnie hackowanie memory streama, z tym zwiększaniem Capacity przy przekraczaniu o 2x (tak jak List<.T>), gdzie niekoniecznie aż tak dużo trzeba.

https://stackoverflow.com/questions/24636259/why-does-c-sharp-memory-stream-reserve-so-much-memory

1 użytkowników online, w tym zalogowanych: 0, gości: 1