901

I have a StreamReader object that I initialized with a stream, now I want to save this stream to disk (the stream may be a .gif or .jpg or .pdf).

Existing Code:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. I need to save this to disk (I have the filename).
  2. In the future I may want to store this to SQL Server.

I have the encoding type also, which I will need if I store it to SQL Server, correct?

1
  • 7
    What is myOtherObject? Commented Aug 8, 2019 at 7:09

10 Answers 10

1150

As highlighted by Tilendor in Jon Skeet's answer, streams have a CopyTo method since .NET 4.

var fileStream = File.Create("C:\\Path\\To\\File");
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
fileStream.Close();

Or with the using syntax:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}

You have to call Seek if you're not already at the beginning or you won't copy the entire stream.

Sign up to request clarification or add additional context in comments.

7 Comments

If this input stream is got from http connection then will it buffer and download and then write all the bytes from the source?????
I have created PDF viewer where I am using stream, once I bind the stream and when I save the pdf file using the same stream then without using "Seek(0, SeekOrigin.Begin)" I wont be able to save correct document. so +1 for mentioning this "Seek(0, SeekOrigin.Begin)"
myOtherObject.InputStream.CopyTo(fileStream); this line gives an error: access denied.
@sulhadin that just means that you don't have permission to write on fileStream
Any reason for using .Seek(0, SeekOrigin.Begin) instead of .Position = 0? Since both seem do the same thing in this case.
|
598

You must not use StreamReader for binary files (like gifs or jpgs). StreamReader is for text data. You will almost certainly lose data if you use it for arbitrary binary data. (If you use Encoding.GetEncoding(28591) you will probably be okay, but what's the point?)

Why do you need to use a StreamReader at all? Why not just keep the binary data as binary data and write it back to disk (or SQL) as binary data?

EDIT: As this seems to be something people want to see... if you do just want to copy one stream to another (e.g. to a file) use something like this:

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

To use it to dump a stream to a file, for example:

using (Stream file = File.Create(filename))
{
    CopyStream(input, file);
}

Note that Stream.CopyTo was introduced in .NET 4, serving basically the same purpose.

8 Comments

This seems like such a common case I'm surprised its not in .NET. I see people creating byte arrays the size of the entire file, which can cause problems for big files.
@Tilendor: It's present as an extension method in .NET 4. (CopyTo)
I don't think it is an extension method, but it's new in Stream class.
@Kugel: You're right, sorry. I had it as an extension method in a utility library, but now that it's in Stream itself, my extension method doesn't get called.
@Florian: It's reasonably arbitrary - a small enough value to avoid taking too much memory, and large enough to transfer a reasonable chunk at a time. It would be fine to be 16K, 32K maybe - I'd just be careful not to end up on the large object heap.
|
108
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}

5 Comments

You probably shouldn't put the stream object in the using(){} bracket. Your method didn't create the stream, so it shouldn't dispose of it.
Instead you need to put FileStream instead to using, otherwise it will be kept open until it is garbage collected.
This ran fine but I got a 0 KB output. Instead I had to do this for the correct output: File.WriteAllBytes(destinationFilePath, input.ToArray());. In my case, input is a MemoryStream coming from within a ZipArchive.
If stream might not be at the beginning, do stream.Position = 0; as first line of this method.
@ToolmakerSteve that's exactly what the issue is. You absolutely need stream.Position = 0; or you need to call stream.Seek(0, SeekOrigin.Begin);
35
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}

3 Comments

This ran fine but I got a 0 KB output. Instead I had to do this for the correct output: File.WriteAllBytes(destinationFilePath, input.ToArray());. In my case, input is a MemoryStream coming from within a ZipArchive.
This helped me figure out what I was doing wrong. However, don't forget to move to the beginning of the stream: stream.Seek(0, SeekOrigin.Begin);
stream.Position = 0; is an alternative syntax for moving to the beginning of the stream.
12

I don't get all of the answers using CopyTo, where maybe the systems using the app might not have been upgraded to .NET 4.0+. I know some would like to force people to upgrade, but compatibility is also nice, too.

Another thing, I don't get using a stream to copy from another stream in the first place. Why not just do:

byte[] bytes = myOtherObject.InputStream.ToArray();

Once you have the bytes, you can easily write them to a file:

public static void WriteFile(string fileName, byte[] bytes)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write))
    {
        fs.Write(bytes, 0, (int)bytes.Length);
        //fs.Close();
    }
}

This code works as I've tested it with a .jpg file, though I admit I have only used it with small files (less than 1 MB). One stream, no copying between streams, no encoding needed, just write the bytes! No need to over-complicate things with StreamReader if you already have a stream you can convert to bytes directly with .ToArray()!

Only potential downsides I can see in doing it this way is if there's a large file you have, having it as a stream and using .CopyTo() or equivalent allows FileStream to stream it instead of using a byte array and reading the bytes one by one. It might be slower doing it this way, as a result. But it shouldn't choke since the .Write() method of the FileStream handles writing the bytes, and it's only doing it one byte at a time, so it won't clog memory, except that you will have to have enough memory to hold the stream as a byte[] object. In my situation where I used this, getting an OracleBlob, I had to go to a byte[], it was small enough, and besides, there was no streaming available to me, anyway, so I just sent my bytes to my function, above.

Another option, using a stream, would be to use it with Jon Skeet's CopyStream function that was in another post - this just uses FileStream to take the input stream and create the file from it directly. It does not use File.Create, like he did (which initially seemed to be problematic for me, but later found it was likely just a VS bug...).

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

public static void WriteFile(string fileName, Stream inputStream)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write)
    {
        CopyStream(inputStream, fs);
    }

    inputStream.Close();
    inputStream.Flush();
}

6 Comments

No need to call Close because of using()
@Alex78191 If you're talking about inputStream.Close(), look again - inputStream is sent in as a variable. The using is on the path+filename output stream. If you were talking about fs.Close() in the middle of the using, sorry, you were correct about that and I removed that.
Should flush before you close. Although close should do a flush too.
@Andrew I think that's why I did them in the order I did - because I don't think you can do a .Close() on a stream that has been flushed because .Flush() closes it, too, and I wanted to do both commands.
There is no Stream.ToArray() method, so where is that coming from? As for why you'd use a Stream when you could just use a byte[], I'd ask why you'd introduce a(n intermediate) byte[] when you already have a Stream? You mentioned the downside of full-Stream buffering to a byte[] — memory implications — but what's the upside? Also, this answer starts off questioning the usefulness of Stream-to-Stream copying but then suggests CopyStream(), which is implemented the same way as CopyTo().
|
7

Why not use a FileStream object?

public void SaveStreamToFile(string fileFullPath, Stream stream)
{
    if (stream.Length == 0) return;

    // Create a FileStream object to write a stream to a file
    using (FileStream fileStream = System.IO.File.Create(fileFullPath, (int)stream.Length))
    {
        // Fill the bytes[] array with the stream data
        byte[] bytesInStream = new byte[stream.Length];
        stream.Read(bytesInStream, 0, (int)bytesInStream.Length);

        // Use FileStream object to write to the specified file
        fileStream.Write(bytesInStream, 0, bytesInStream.Length);
     }
}

4 Comments

what if the input stream is 1GB long - this code would try to allocate 1GB buffer :)
This is not working with ResponseStream, because it is of uknown length.
While it's true you'd have to have the memory available for the byte[], I think it would be rare that you'd be streaming a 1 GB+ blob to a file...unless you have a site that keeps DVD torrents... Plus, most computers have at least 2 GB of RAM available these days, anyway....Caveat is valid, but I think this is a case where it's probably "good enough" for most jobs.
Webservers won't tolerate a case like this very well at all, unless the website only has a single user active at once.
7

Another option is to get the stream to a byte[] and use File.WriteAllBytes. This should do:

using (var stream = new MemoryStream())
{
    input.CopyTo(stream);
    File.WriteAllBytes(file, stream.ToArray());
}

Wrapping it in an extension method gives it better naming:

public void WriteTo(this Stream input, string file)
{
    //your fav write method:

    using (var stream = File.Create(file))
    {
        input.CopyTo(stream);
    }

    //or

    using (var stream = new MemoryStream())
    {
        input.CopyTo(stream);
        File.WriteAllBytes(file, stream.ToArray());
    }

    //whatever that fits.
}

1 Comment

If the input is too large you'll get an out of memory exception. The option of copying content from the input stream to a filestream is much better
7

Here's an example that uses proper usings and implementation of idisposable:

static void WriteToFile(string sourceFile, string destinationfile, bool append = true, int bufferSize = 4096)
{
    using (var sourceFileStream = new FileStream(sourceFile, FileMode.OpenOrCreate))
    {
        using (var destinationFileStream = new FileStream(destinationfile, FileMode.OpenOrCreate))
        {
            while (sourceFileStream.Position < sourceFileStream.Length)
            {
                destinationFileStream.WriteByte((byte)sourceFileStream.ReadByte());
            }
        }
    }
}

...and there's also this

    public static void WriteToFile(Stream stream, string destinationFile, int bufferSize = 4096, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.ReadWrite)
    {
        using (var destinationFileStream = new FileStream(destinationFile, mode, access, share))
        {
            while (stream.Position < stream.Length) 
            {
                destinationFileStream.WriteByte((byte)stream.ReadByte());
            }
        }
    }

The key is understanding the proper use of using (which should be implemented on the instantiation of the object that implements idisposable as shown above), and having a good idea as to how the properties work for streams. Position is literally the index within the stream (which starts at 0) that is followed as each byte is read using the readbyte method. In this case I am essentially using it in place of a for loop variable and simply letting it follow through all the way up to the length which is LITERALLY the end of the entire stream (in bytes). Ignore in bytes because it is practically the same and you will have something simple and elegant like this that resolves everything cleanly.

Keep in mind, too, that the ReadByte method simply casts the byte to an int in the process and can simply be converted back.

I'm gonna add another implementation I recently wrote to create a dynamic buffer of sorts to ensure sequential data writes to prevent massive overload

private void StreamBuffer(Stream stream, int buffer)
{
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        var memoryBuffer = memoryStream.GetBuffer();

        for (int i = 0; i < memoryBuffer.Length;)
        {
            var networkBuffer = new byte[buffer];
            for (int j = 0; j < networkBuffer.Length && i < memoryBuffer.Length; j++)
            {
                networkBuffer[j] = memoryBuffer[i];
                i++;
            }
            //Assuming destination file
            destinationFileStream.Write(networkBuffer, 0, networkBuffer.Length);
        }
    }
}

The explanation is fairly simple: we know that we need to keep in mind the entire set of data we wish to write and also that we only want to write certain amounts, so we want the first loop with the last parameter empty (same as while). Next, we initialize a byte array buffer that is set to the size of what's passed, and with the second loop we compare j to the size of the buffer and the size of the original one, and if it's greater than the size of the original byte array, end the run.

2 Comments

FWIW: Jon Skeet showed a higher-performance way to do the second snippet, using Read/Write methods that take a length (instead of one byte at a time). The third snippet is overkill - makes a memory stream to hold all the data - not practical for large data. Again, see Jon Skeet's second snippet. It has that same characteristic, of writing a chunk of data at a time. It does this WITHOUT pulling all the data into memory, and with much simpler code.
Eh that's fair. This is from when I was first working with C# so no worries on the correction. And I agree chunks v byte is better.
6
//If you don't have .Net 4.0  :)

public void SaveStreamToFile(Stream stream, string filename)
{  
   using(Stream destination = File.Create(filename))
      Write(stream, destination);
}

//Typically I implement this Write method as a Stream extension method. 
//The framework handles buffering.

public void Write(Stream from, Stream to)
{
   for(int a = from.ReadByte(); a != -1; a = from.ReadByte())
      to.WriteByte( (byte) a );
}

/*
Note, StreamReader is an IEnumerable<Char> while Stream is an IEnumbable<byte>.
The distinction is significant such as in multiple byte character encodings 
like Unicode used in .Net where Char is one or more bytes (byte[n]). Also, the
resulting translation from IEnumerable<byte> to IEnumerable<Char> can loose bytes
or insert them (for example, "\n" vs. "\r\n") depending on the StreamReader instance
CurrentEncoding.
*/

1 Comment

Copying a stream byte-by-byte (using ReadByte/WriteByte) will be much slower than copying buffer-by-buffer (using Read(byte[], int, int)/Write(byte[], int,int)).
4
public void testdownload(stream input)
{
    byte[] buffer = new byte[16345];
    using (FileStream fs = new FileStream(this.FullLocalFilePath,
                        FileMode.Create, FileAccess.Write, FileShare.None))
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
             fs.Write(buffer, 0, read);
        }
    }
}

2 Comments

Supplying a buffered input stream directly to the FileStream - nice!
This is essentially what Jon Skeet showed in 2009. He just refactored it into two parts, so that one could re-use the stream copying portion with any type of destination stream, not just a file.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.