Read files twice

Asked

Viewed 252 times

3

I have a project Asp.Net MVC in .Net Framework 4.0, with Entity Framework 5.0.

I get multiple files in one method POST and I need to turn them into byte[] to save to Database (VARBINARY(MAX) - SQL Server) and then read its contents.

I’m using the BinaryReader to read the bytes of the entire file and has worked perfectly if I use it before the StreamReader, but when trying to use the StreamReader then to read the lines of the file is as if it had been given a Dispose() in the postedFile.InputStream.

The interesting thing is that if I reverse the order, reading the lines before with StreamReader and then trying to use the BinaryReader, then the latter also does not work on account of the same reason.

I know you both somehow implement the interface IDisposable, and are using the same postedFile.InputStream (which is also Disposable), but what I don’t see the point is to give a Dispose() also in the InputStream and not only on the object itself that is using it.

Follows excerpt from the code:

for (var i = 0; i < Request.Files.Count; i++)
{
    var postedFile = Request.Files[i];
    var fileExtension = Path.GetExtension(postedFile.FileName).ToLower();
    string fileName = Path.GetFileName(postedFile.FileName);

    // Ler bytes do arquivo
    byte[] fileBytes;
    using (var reader2 = new BinaryReader(postedFile.InputStream))
    {
        fileBytes = reader2.ReadBytes(postedFile.ContentLength); // Funciona perfeitamente e retorna o resultado correto.
    }

    // Ler linhas do arquivo
    using (var reader = new StreamReader(postedFile.InputStream))
    {
         var header = reader.ReadLine(); // Erro nessa linha! header = null.
         // Código continua...
    }

    // Código continua...
}
  • Actually it didn’t work, the Length of Inputstream is perfect when I enter the Binaryreader, but when I exit it is 0. As if I had given a Dispose() even, but I think that’s exactly what happens. The solution is in the . Net Framework 4.5, but I’m using 4.0: https://msdn.microsoft.com/en-us/library/gg712952(v=VS.110). aspx

2 answers

1

The problem is in using

Notice you’re doing two using on top of the same stream, yet indirectly:

using (new BinaryReader(postedFile.InputStream))
{
}
// Dispose

using (new StreamReader(postedFile.InputStream))
{
}
// Dispose

So, yes, the postedFile.InputStream will be closed/deleted after the first using.

Solving with byte[] and MemoryStream

for ( var i = 0 ; i < Request.Files.Count ; i++ )
{
    // Bytes do arquivo
    byte[] fileBytes = new byte[postedFile.ContentLength];
    postedFile.InputStream.Read( fileBytes , 0 , fileBytes.Length );

    // Grava dados no banco
    //     A partir de fileBytes

    // Ler linhas do arquivo
    using ( var reader = new StreamReader( new MemoryStream( fileBytes ) ) )
    {
        var header = reader.ReadLine();
        // Seu código
    }

    // Código continua...
}

Thus the data is not closed by using, and you can repeatedly use the array fileBytes, whose which has not Dispose() and you never miss the reference.

However, it keeps duplicated data in memory, for all the processing time of the request, which is a waste.

To free the memory of the postedFile.InputStream As soon as possible, you can make a

using ( postedFile.InputStream )
    ;

shortly after the Read(). Unusual but effective.


Other considerations

Httpinputstream and performance

The estate InputStream in Request.Files (class HttpPostedFile) is not an exactly conventional stream. It is the class HttpInputStream, that internally used still HttpRawUploadedContent as storage, in order to handle data in memory or data in temporary files, transparently.

This more the question than in namespace System.Web the concern is all with performance and simultaneous accesses, parallel, it is something expected that ephemeral data are discarded as soon as possible. The using just making it happen before.

The balcony there is that the using is acting indirectly on a given that has been created outside, before of own using.

Memorystream for "small data"

One solution to your problem is to read the entire contents of the file to a MemoryStream, and from there make repeated readings (as in the above example).

It’s a shame to duplicate the data temporarily (and even more if they are large), but it’s the way to deal with this issue.

Temporary files for large data

However, if you expect to receive large files, which are entire percentages of the server’s RAM, hence the implementation of HttpRawUploadedContent gives a hint: do not leave this data in memory, put in temporary files.


Httpinputstream and Httprawuploadedcontent source codes

  • Thank you very much for your reply. All the information was very well put by you! I know the link can help me, but unfortunately it can break, so it would be spectacular if you could exemplify a small chunk of code...

  • Another issue is that unfortunately I am not allowed to write files to server directories, so HttpRawUploadedContent I believe it could not be used in my case, but I did not know and liked it very much, and I also believe it can help a lot other people with the same problem.

  • HttpRawUploadedContent would only be to inspire, since it is a class internal and therefore unavailable. I put the example, but I will complement the answer, which I think the problem is in the using.

  • 1

    Look how it turned out.

0


Really, from what I understand in the polls, both BinaryReader as to the StreamReader when they leave the context of using, automatically make an "auto" Dispose() and also of all who are IDisposable used inside.

This was improved in version 4.5 of . Net Framework, including the option leaveOpen in the maker of StreamReader for example, that when true keeps the objects open.

I had to come up with a different solution for myself:

byte[] fileBytes;
using (var ms = new MemoryStream())
{
    // Copia o InputStream para o MemoryStream 'ms'.
    postedFile.InputStream.CopyTo(ms); 

    // Reposiciona para o início o ponteiro de leitura que foi para o final após a realização da cópia.
    ms.Position = 0; 

    // Crio 'ms2' que será 'morto' depois que passar pelo BinaryReader
    using (var ms2 = new MemoryStream())
    {
         ms.CopyTo(ms2);
         ms2.Position = 0;

         using (var br = new BinaryReader(ms2))
         {
              fileBytes = br.ReadBytes((int)ms2.Length);
         }
    }

    // A Position mudou pois foi feita a cópia para 'ms2'
    ms.Position = 0;

    // Aqui 'ms' será lido e 'morrerá'
    using (var reader = new StreamReader(ms))
    {
        var header = reader.ReadLine();
    }
}

Source:

https://stackoverflow.com/a/8462655/4720858

Browser other questions tagged

You are not signed in. Login or sign up in order to post.