Search code examples
c#filestreammemorystream

Changes are not save when using MemoryStream instead of FileStream


I have a DLL with embedded Excel file. The goal is to retrieve this file and create some entry (Empty_File.txt in this example). When I'm using FileStream - the entry gets created, but when I'm using MemoryStream - entry isn't created.

var filePath = "C:\\Temp\\Test2.xlsx";
var asm = typeof(Program).Assembly;
var asmName = asm.GetName().Name;
using var resourceStream = asm.GetManifestResourceStream($"{asmName}.Resources.Template.xlsx");

if (File.Exists(filePath)) File.Delete(filePath);

await UseFileStream(resourceStream, filePath);
// or
await UseMemoryStream(resourceStream, filePath);


static async Task UseMemoryStream(Stream resourceStream, string filePath)
{
  using (var ms = new MemoryStream())
  {
    await resourceStream.CopyToAsync(ms);
    using (var zip = new ZipArchive(ms, ZipArchiveMode.Update))
    {
      zip.CreateEntry("Empty_File.txt");
      using (var fs = CreateFileStream(filePath))
      {
        ms.Seek(0L, SeekOrigin.Begin);
        await ms.CopyToAsync(fs);
      }
    }
  }
}

static async Task UseFileStream(Stream resourceStream, string filePath)
{
  using var fs = CreateFileStream(filePath);
  await resourceStream.CopyToAsync(fs);
  using var zip = new ZipArchive(fs, ZipArchiveMode.Update);
  zip.CreateEntry("Empty_File.txt");
}

static FileStream CreateFileStream(string filePath) =>
  new FileStream(filePath, new FileStreamOptions
  {
    Access = FileAccess.ReadWrite,
    Mode = FileMode.Create,
    Share = FileShare.None
  });

Solution

  • Per the docs for ZipArchive.Dispose:

    This method finishes writing the archive and releases all resources used by the ZipArchive object. Unless you construct the object by using the ZipArchive(Stream, ZipArchiveMode, Boolean) constructor overload and set its leaveOpen parameter to true, all underlying streams are closed and no longer available for subsequent write operations.

    You are currently writing to the file stream before this happens, so the changes to the zip file haven't been written yet.

    You'll also note from this that the underlying MemoryStream will be disposed unless you specify leaveOpen: true in the constructor, which would prevent you copying to the file afterwards.

    So putting both of these together:

    static async Task UseMemoryStream(Stream resourceStream, string filePath)
    {
        using (var ms = new MemoryStream())
        {
            await resourceStream.CopyToAsync(ms);
            using (var zip = new ZipArchive(ms, ZipArchiveMode.Update, leaveOpen: true))
            {
                zip.CreateEntry("Empty_File.txt");
            }
            using (var fs = CreateFileStream(filePath))
            {
                ms.Seek(0L, SeekOrigin.Begin);
                await ms.CopyToAsync(fs);
            }
        }
    }