4

I'm trying to serve a txt file made from the database using an action. The action is the following:

public ActionResult ATxt()
{
    var articulos = _articulosService.ObteTotsArticles();
    return File(CatalegATxt.ATxt(articulos), "text/plain");
}

and the CatalegATxt class is:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using WebDibaelsaMVC.DTOs.Busqueda;

namespace WebDibaelsaMVC.TxtLib
{
    public static class CatalegATxt
    {
         public static Stream ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
         {
            var stream = new MemoryStream();
            var streamWriter = new StreamWriter(stream, Encoding.UTF8);
            foreach (ArticuloBusquedaDTO article in articles)
            {
                streamWriter.WriteLine(article.ToStringFix());
            }
            stream.Seek(0, SeekOrigin.Begin);
            return stream;
        }

        public static string ToStringFix(this ArticuloBusquedaDTO article)
        {
            string result = "";
            result += article.CodigoArticulo.PadRight(10, ' ').Substring(0, 10);
            result += article.EAN.Trim().PadLeft(13, '0').Substring(0, 13);
            result += article.NombreArticulo.PadRight(100, ' ').Substring(0, 100);
            result += article.Marca.PadRight(100, ' ').Substring(0, 100);
            result += article.Familia.PadRight(50, ' ').Substring(0, 50);
            result += article.PrecioCesion.ToStringFix();
            result += article.PVP.ToStringFix();
            return result;
        }

        private static string ToStringFix(this double numero)
        {
            var num = (int)Math.Round(numero * 100, 0);
            string result = num.ToString().PadLeft(10, '0');
            return result;
        }
    }
}

it just writes the file lines based on the stuff I got from the database. But when I look at the file it looks truncated. The file is about 8Mb. I also tried converting to byte[] before returning from ATxt with the same result.

Any idea?

Thanks,

Carles

Update: I also tried to serve XML from the same content and it also gets truncated. It doesn't get truncated on the data (I thought it might have been an EOF character in it) but it truncates in the middle of a label...

Carles Company
  • 7,118
  • 5
  • 49
  • 75
  • Quick Q, For both txt and xml tests are the returned file sizes the same length? This might help us track it down. – Rippo Dec 30 '09 at 10:02
  • If you trace the content of CatelegAText.ATxt in your ActionResult method above, does it contain the correct data before you return it? – glenatron Dec 30 '09 at 10:19
  • @Rippo: They aren't the same length. txt is some Mb, xml is less than 1Mb. – Carles Company Dec 30 '09 at 11:17
  • @glenatron: yes, it does contain the complete data. Also, it always stop at the same item for each file. But the item it stops in the XML file is not the same as the item it stops for the txt file. Weird... – Carles Company Dec 30 '09 at 11:18
  • I would now do 2 things 1) Write out a file from disk (other content completely) to see if it is a web server issue and 2) Move the code to another machine to see if you can replicate it. – Rippo Dec 30 '09 at 11:38
  • @Rippo: I tried the code on another server and it did exactly the same... Same file length, wrong item... – Carles Company Dec 30 '09 at 23:06
  • I'm thinking that maybe it's something about the StreamWriter (maybe not closing it) or some timeout on the server (it's a pretty heavyloaded server/little machine). – Carles Company Dec 30 '09 at 23:09

3 Answers3

6

I was having the exact same problem. The text file would always be returned as truncated.

It crossed my mind that it might be a "flushing" problem, and indeed it was. The writer's buffer hasn't been flushed at the end of the operation - since there's no using block, or the Close() call - which would flush automatically.

You need to call:

streamWriter.Flush();

before MVC takes over the stream.

Here's how your method should look like:

 public static Stream ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
 {
    var stream = new MemoryStream();
    var streamWriter = new StreamWriter(stream, Encoding.UTF8);
    foreach (ArticuloBusquedaDTO article in articles)
    {
        streamWriter.WriteLine(article.ToStringFix());
    }
    // Flush the stream writer buffer
    streamWriter.Flush();
    stream.Seek(0, SeekOrigin.Begin);
    return stream;
}
Miroslav Popovic
  • 12,100
  • 2
  • 35
  • 47
3

Why are you using an ActionResult?

ASP.NET MVC 1 has a FileStreamResult for just what you are doing. It expects a Stream object, and returns it.

public FileStreamResult Test()
{
  return new FileStreamResult(myMemoryStream, "text/plain");
}

Should work fine for what you want to do. No need to do any conversions.

In your case, just change your method to this:

public FileStreamResult ATxt()
{
    var articulos = _articulosService.ObteTotsArticles();
    return new FileStreamResult(CatalegATxt.ATxt(articulos), "text/plain");
}
thorkia
  • 1,972
  • 1
  • 20
  • 26
  • I thought that `FileStreamResult` extends `ActionResult` and it is the same. Also the function `File` returns a `FileStreamResult`... – Carles Company Dec 24 '09 at 14:03
  • FileStreamResult extends FileResult. It is specifically designed to send a stream to the client. I have used it regularly to send excel files to users that are in the neighbourhood of 10-15 megs and never have them truncated. – thorkia Dec 24 '09 at 15:23
  • FileResult does extend ActionResult, but they handle output differently. If you want to send a file that is stored on the hard drive, you should use FileResult If you want to send the contents of a file, you should use FileStreamResult – thorkia Dec 24 '09 at 15:25
  • 1
    If you don't want a perfectly good answer, don't ask the question? – TFD Dec 30 '09 at 07:40
  • This answer does not answer my question... If you look at my method you'll see I'm returning a FileStreamResult. What if depending on the parameters I want to return a different type of ActionResult? Does it not work then? For the record, I tried what this answer sugests and I get the same result... – Carles Company Dec 30 '09 at 08:17
  • I'm starting to think that my question might not be correct and the problem is elsewhere. – Carles Company Dec 30 '09 at 08:18
0

You probably want to close the MemoryStream. It could be getting truncated because it expects more data still. Or to make things even simpler, try something like this:

public static byte[] ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
{
   using(var stream = new MemoryStream())
   {
        var streamWriter = new StreamWriter(stream, Encoding.UTF8);
        foreach (ArticuloBusquedaDTO article in articles)
        {
            streamWriter.WriteLine(article.ToStringFix());
        }

        return stream.ToArray();
    }
}
Jedidja
  • 16,610
  • 17
  • 73
  • 112
  • I already tried to do this with the same result. Also, closing the stream before returning it results in an error (the view tries to acces a closed stream...) – Carles Company Dec 24 '09 at 14:05