How do I save a stream to a file in C#?
Asked Answered
T

10

880

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?

Tia answered 4/1, 2009 at 20:4 Comment(1)
What is myOtherObject?Beaulahbeaulieu
L
1118

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.

Laughingstock answered 1/4, 2011 at 16:1 Comment(7)
If this input stream is got from http connection then will it buffer and download and then write all the bytes from the source?????Basilius
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)"Undistinguished
myOtherObject.InputStream.CopyTo(fileStream); this line gives an error: access denied.Summon
@Summon that just means that you don't have permission to write on fileStreamLaughingstock
Any reason for using .Seek(0, SeekOrigin.Begin) instead of .Position = 0? Since both seem do the same thing in this case.Leipzig
@MartinSchneider Good question. I don't use C# that much these days, so I can't say. Reading the link you provided, it seems that you're right that we could use .Position = 0, and I would probably prefer that syntax as well.Laughingstock
This stream does not support seek operations :(Compact
S
591

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.

Strapped answered 4/1, 2009 at 20:8 Comment(8)
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.Washy
@Tilendor: It's present as an extension method in .NET 4. (CopyTo)Strapped
I don't think it is an extension method, but it's new in Stream class.Intrigante
@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.Strapped
Why you set the size of the buffer to 8192 ? ThxLasandralasater
@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.Strapped
@JonSkeet I see the default buffer size for Stream.CopyTo is _DefaultCopyBufferSize = 81920; so it is probably safe to assume one can use it like new byte[81920]; everywhere and even in Java like new byte[65536]; (Also a multiple of 4096)?Starbuck
@Pierre: Multiples of 4K probably don't make much odds, although it will depend on the stream. (And Java doesn't have a 64K limit for byte arrays... I'm not sure why you're suggesting a difference there.) But as ever, you need to balance memory allocation with fewer copies being made. Given that it's not a long-lived buffer, it probably doesn't matter too much, but it can depend on your application.Strapped
C
104
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}
Carrnan answered 1/8, 2013 at 20:25 Comment(4)
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.Chrysostom
Instead you need to put FileStream instead to using, otherwise it will be kept open until it is garbage collected.Giavani
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.Dardanus
If stream might not be at the beginning, do stream.Position = 0; as first line of this method.Patent
D
35
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}
Dedication answered 11/12, 2013 at 14:38 Comment(3)
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.Dardanus
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);Liberia
stream.Position = 0; is an alternative syntax for moving to the beginning of the stream.Patent
K
11

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();
}
Kenzi answered 12/10, 2016 at 13:57 Comment(6)
No need to call Close because of using()Derina
@Derina 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.Kenzi
Should flush before you close. Although close should do a flush too.Histamine
@Histamine 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.Kenzi
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().Asti
@LanceU.Matthews Who said anything about doing Stream.ToArray()? Read the code. It says InputStream before that! It is off of that method, not the original stream. Byte objects can be manipulated far easier and can be stored. THAT is the upside. A stream has to be written first, so you can do those things to the underlying data. So if you're going to have to do that, anyway, why use it? Just use/copy it as a byte object. And NO, CopyStream() is writing it to a byte[], not another stream! So no, it's NOT just like CopyTo(). You're just not understanding any of this, are you?Kenzi
I
8

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.

Impermissible answered 27/10, 2019 at 8:48 Comment(2)
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.Patent
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.Impermissible
K
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);
     }
}
Kwakiutl answered 23/4, 2010 at 22:49 Comment(4)
what if the input stream is 1GB long - this code would try to allocate 1GB buffer :)Worked
This is not working with ResponseStream, because it is of uknown length.Collation
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.Kenzi
Webservers won't tolerate a case like this very well at all, unless the website only has a single user active at once.Greeson
R
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.
*/
Retiform answered 7/7, 2011 at 14:30 Comment(1)
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)).Compile
F
6

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.
}
Folklore answered 13/2, 2014 at 11:4 Comment(1)
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 betterChartulary
D
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);
        }
    }
}
Dreyfus answered 16/7, 2013 at 6:47 Comment(2)
Supplying a buffered input stream directly to the FileStream - nice!Kenzi
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.Patent

© 2022 - 2024 — McMap. All rights reserved.