ZipArchieve fails with ArgumentException when stream position is reset - c#

I have multiple Stream instances and would like to zip them up using ZipArchive. This is the code I'm using:
using var memoryStream = new MemoryStream();
using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var stream in streams)
{
var entry = zipArchive.CreateEntry($"{Guid.NewGuid()}.jpeg");
using var entryStream = entry.Open();
stream.CopyTo(entryStream);
}
}
memoryStream.Seek(0, SeekOrigin.Begin);
// memoryStream.Position = 0; // this throws the same exception
This code works fine when streams has a few items, but it fails with 10 items. The error is coming from the last line when I try to rewind back to the initial position.
{System.ArgumentException: Offset and length were out of bounds for
the array or count is greater than the number of elements from index
to the end of the source collection. at System.IO.MemoryStream.Read
(System.Byte[] buffer, System.Int32 offset, System.Int…}
This is the memory stream state in debugger:
CanRead: true
CanSeek: true
CanWrite: true
Capacity: 8534016
Length: 6853402
Position: 6853402
Note that this code is running on a physical iOS device written in Xamarin iOS.
What's causing this error to be thrown?

Related

FileStream.WriteAsync(buffer.AsMemory(0, read)) won't work when writing small files but CopyToAsync will

I have small sig files that are exactly 256 bytes. When uploading to a file upload controller on asp.net core web app, the buffer is occupied correctly for the 256 positions but they aren't written to the output stream and the file is empty. CopyToAsync works fine. This will only happen to certain files. The problem is reproducible on a console application:
string SoureFile = #"C:\Users\me\source\repos\files\mySigFile.sig";
byte[] buffer = new byte[1024 * 64];
string tmp = #"C:\Users\me\Downloads\tempsigfile.tmp";
string tmp2 = #"C:\Users\me\Downloads\tempsigfile2.tmp";
var inputStream = File.Open(SoureFile, FileMode.OpenOrCreate);
//doesn't work
using FileStream writer = new(tmp, FileMode.Create);
int read;
while ((read = await inputStream.ReadAsync(buffer)) != 0)
{
await writer.WriteAsync(buffer.AsMemory(0, read));
}
inputStream.Position = 0;
//works
using (var stream = new FileStream(tmp2, FileMode.Create))
{
await inputStream.CopyToAsync(stream);
}
FileInfo info = new FileInfo(tmp);
Console.WriteLine(info.Length); //0
FileInfo info2 = new FileInfo(tmp2);
Console.WriteLine(info2.Length);//256
Doing this (using declaration, no bracers):
using FileStream writer = new(tmp, FileMode.Create);
means writer will only be disposed at the end of the scope, so at the end of the method. Doing WriteAsync does not necessary mean that information will be written to file right away, it might be written to the internal in-memory buffer and only written to the file when this buffer fills or when you close the file, or when you explicitly call Flush on a stream. You don't do anything of that, the file is only closed at the end of the method, but your check:
FileInfo info = new FileInfo(tmp);
Console.WriteLine(info.Length); //0
is performed before the actual write to file happens, so you see 0. If you actually check the contents of the file after this method (program) completes - you'll see it contains the correct data.
In second case you use using statement:
using (var stream = new FileStream(tmp2, FileMode.Create))
{
await inputStream.CopyToAsync(stream);
}
so you write to a file, close it, and only after that check the contents. Then it works as you expect.

memorystream(byte[]) vs memorystream.write(byte[])

I needed to put a byte to a memory stream so initially, I used:
byte[] Input;
using (MemoryStream mem = new MemoryStream())
{
mem.Write(Input, 0, (int)Input.Length);
StreamReader stream = new StreamReader(mem);
...
}
I wanted to use the Streamreader to read lines from a text file.
It didn't work.
Then I used
using (MemoryStream mem = new MemoryStream(Input))
instead and removed
mem.Write(Input, 0, (int)Input.Length);
It worked. I don't know why. Why did it work?
In your first approach, you use mem.Write(Input, 0, (int)Input.Length);. Note that MemoryStream.Write sets the stream read/write position behind the written data. In your example case this is equivalent with a position signifying the end of the stream. Trying to read from the MemoryStream again will not return any data, as the MemoryStream read/write position is at the end of the stream.
In your second approach, you passed the Input byte array as argument to the MemoryStream constructor. Providing the byte array through the constructor not only will make MemoryStream use this byte array, but more importantly it keeps the initial stream position of zero. Thus, when trying to read from the MemoryStream initialized in this way, the data contained in the input byte array will be returned as expected.
How to fix the problem with the first approach?
You can make the first approach with MemoryStream.Write working by simply setting the MemoryStream position back to the intended/original value (in your example case it would be zero) after writing the data to the MemoryStream:
byte[] Input;
using (MemoryStream mem = new MemoryStream())
{
mem.Write(Input, 0, (int)Input.Length);
mem.Position = 0;
using (StreamReader stream = new StreamReader(mem))
{
...
}
}

Stream.CopyTo not copying any stream data

I'm having an issue with copying data from a MemoryStream into a Stream inside a ZipArchive. The following is NOT working - it returns only 114 bytes:
GetDataAsByteArray(IDataSource dataSource)
{
using (var zipStream = new MemoryStream())
{
using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
var file = archive.CreateEntry("compressed.file");
using (var targetStream = file.Open())
{
using (var sourceStream = new MemoryStream())
{
await dataSource.LoadIntoStream(sourceStream);
sourceStream.CopyTo(targetStream);
}
}
}
var result = zipStream.ToArray();
zipStream.Close();
return result;
}
}
However, using the implementation below for the "copy"-process, all 1103 bytes are written to the array/memory stream:
await targetStream.WriteAsync(sourceStream.ToArray(), 0, (int) sourceStream.Length);
I'm wondering why the CopyTo yields less bytes. Also I'm feeling unsecure with the cast to Int32 in the second implementation.
FYI: Comparing the byte array: It looks like only the header and footer of the zip file were written by the first implementation.
Stream.CopyTo() starts copying from the stream's current Position. Which probably isn't 0 after that LoadIntoStream() call. Since it is a MemoryStream, you can simply fix it like this:
await dataSource.LoadIntoStream(sourceStream);
sourceStream.Position = 0;
sourceStream.CopyTo(targetStream);
Set sourceStream.Position = 0 before copying it. The copy will copy from the current position to the end of the stream.
As other have said the Position is probably no longer 0. You can't always set the Position back to 0 though, such as for Network and Compressed streams. You should check the stream.CanSeek property before doing any operations and if it is false then copy the stream to a new MemoryStream first (which can be seeked) and then after each operation which changes the position set the Position back to 0.

Copy MemoryStream and recreate System.Drawing.Image fails with "Parameter is not valid"

I am receiving an ArgumentException (Parameter is not valid) when trying to recreate an image from a memory stream. I have distilled it down to this example where I load up an image, copy to a stream, replicate the stream and attempt to recreate the System.Drawing.Image object.
im1 can be saved back out fine, after the MemoryStream copy the stream is the same length as the original stream.
I am assuming that the ArgumentException means that System.Drawing.Image doesnt think my stream is an image.
Why is the copy altering my bytes?
// open image
var im1 = System.Drawing.Image.FromFile(#"original.JPG");
// save into a stream
MemoryStream stream = new MemoryStream();
im1.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
// try saving - succeeds
im1.Save(#"im1.JPG");
// check length
Console.WriteLine(stream.Length);
// copy stream to new stream - this code seems to screw up my image bytes
byte[] allbytes = new byte[stream.Length];
using (var reader = new System.IO.BinaryReader(stream))
{
reader.Read(allbytes, 0, allbytes.Length);
}
MemoryStream copystream = new MemoryStream(allbytes);
// check length - matches im1.Length
Console.WriteLine(copystream.Length);
// reset position in case this is an issue (doesnt seem to make a difference)
copystream.Position = 0;
// recreate image - why does this fail with "Parameter is not valid"?
var im2 = System.Drawing.Image.FromStream(copystream);
// save out im2 - doesnt get to here
im2.Save(#"im2.JPG");
Before reading from the stream you need to rewind its position to zero. You are doing that for the copy right now, but also need to do that for the original.
Also, you don't need to copy to a new stream at all.
I usually resolve such problems by stepping through the program and looking at runtime state to see whether it matches my expectation or not.

Cannot write to MemoryStream using BinaryWriter

I've tried this code:
byte[] someData = new byte[] { 1, 2, 3, 4 };
MemoryStream stream = new MemoryStream(someData, 1, someData.Length - 1, true);
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(1);
}
stream.Dispose();
Everytime it's run, a NotSupportedException is thrown, telling me that the stream cannot be written to. Why is this the case? The last parameter of the initialization shown in line 2 clearly is true, so I should be able to write to the stream.
It works if I don't specify the start index and count.
Why does this happen?
Always (almost always) create a memory stream without parameters in the constructor:
using (MemoryStream stream = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(1);
}
stream.Flush();
byte[] bytes = stream.GetBuffer();
//use it
}
This code works fine
From MSDN:
Initializes a new non-resizable instance of the MemoryStream class
based on the specified region of a byte array, with the CanWrite
property set as specified.
The BinaryWriter starts writing at the end of the stream, so it needs to resize it to be able to write, but this is not allowed. You can only write to the already allocated bytes of the stream.

Categories