FileResult throws System.ObjectDisposedException: Cannot access a closed file - c#

I'm creating an endpoint that returning File download after it generates an Excel file, I have 2 methods, the first one is to return FileStream object as asynchronous and the second one is to return File download which called from Http.
Many said I have to make the stream seek to the beginning again before it's read by FileResult, but it seems doesn't work.
First method:
private async Task<FileStream> Generate(int projectId, DateTime period)
{
...
if (...)
{
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
...
return fs;
}
}
return null;
}
Second method:
[HttpPost]
public async Task<IActionResult> Index([FromBody]ReportFilter filter)
{
FileStream fs = await Generate(filter.projectId, DateTime.Parse(filter.period));
if (fs != null)
{
fs.Seek(0, SeekOrigin.Begin);
return File(fs, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "kpi.xlsx");
}
return Json(new { status="error", message="Error while processing request" });
}
Unfortunately, it throws:
System.ObjectDisposedException: Cannot access a closed file.
at System.IO.FileStream.Seek(Int64 offset, SeekOrigin origin)
[UPDATE]
Without using block:
private async Task<FileStream> Generate(int projectId, DateTime period)
{
...
if (...)
{
FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write);
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet1 = workbook.CreateSheet("Sheet1");
sheet1.AddMergedRegion(new CellRangeAddress(0, 0, 0, 10));
var rowIndex = 0;
IRow row = sheet1.CreateRow(rowIndex);
row.Height = 30 * 80;
var cell = row.CreateCell(0);
var font = workbook.CreateFont();
font.IsBold = true;
font.Color = HSSFColor.DarkBlue.Index2;
cell.CellStyle.SetFont(font);
cell.SetCellValue("A very long piece of text that I want to auto-fit innit, yeah. Although if it gets really, really long it'll probably start messing up more.");
sheet1.AutoSizeColumn(0);
rowIndex++;
workbook.Write(fs);
return fs;
}
return null;
}
[UPDATE]
Using jalsh's suggestion (by reopening the FileStream while preparing a download):
if (System.IO.File.Exists(filename))
{
FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
return File(fs, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "kpi.xlsx");
}

Often when you want to use a disposable object like this it is better to inject in the action required, rather that to expose the disposable object outside of the method that creates it.
I've simplified your code down, but this is the basic idea:
private async Task Generate(int projectId, DateTime period, Action<FileStream> operation)
{
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
operation(fs);
}
}
Now you call it like this:
public async Task Index(int projectId, string period)
{
await Generate(projectId, DateTime.Parse(period), fs =>
{
if (fs != null)
{
fs.Seek(0, SeekOrigin.Begin);
return File(fs, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "kpi.xlsx");
}
});
}
This allows operation(fs) to complete before the end of the using statement.

The using statement calls dispose() at the end of its scope. see MSDN link, it has a case just like yours...
you could either not use the using statement and dispose of the object manually whenever you're done with it. Or you could just reopen the filestream again, or maybe return a File Instance from your method that you could reopen the stream with
Now that you shared your full code, it seems to me that your Write call is disposing the FileStream or closing it, you can try reopen the filestream just after you do the Write() call.

Related

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
});
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);
}
}
}

MemoryStream.ToArray() return empty array if StreamWriter is not disposed

I'm trying to get array of bytes from my model to put it in the file. I have a method as such:
public static byte[] GetByteArray(List<MyModel> models)
{
using var ms = new MemoryStream();
using var sw = new StreamWriter(ms);
foreach (var model in models)
{
sw.Write(model.Id + "," + model.Name);
sw.WriteLine();
}
sw.Dispose();
return ms.ToArray();
}
This method works fine, but as may think I don't need to dispose StreamWriter manually, cause I have a using statement. I thought as well, but when I remove sw.Dispose(); the ms.ToArray(); returns an empty array. Can someone explain this behavior to me?
You have the line:
using var sw = new StreamWriter(ms);
This only disposes the StreamWriter at the end of the method. However you're calling ms.ToArray() before the end of the method. This means that you're calling ms.ToArray() before the StreamWriter is disposed.
However, the StreamWriter is buffering some data internally, and only flushes this out to the MemoryStream when it is disposed. You therefore need to make sure you dispose the StreamWriter before calling ms.ToArray().
It's probably clearer to use the older using syntax, which is explicit about when the disposal happens:
public static byte[] GetByteArray(List<MyModel> models)
{
using var ms = new MemoryStream();
using (var sw = new StreamWriter(ms))
{
foreach (var model in models)
{
sw.Write(model.Id + "," + model.Name);
sw.WriteLine();
}
}
return ms.ToArray();
}
The dispose does part of the job. It flushes the writer. Use Flush() to flush it manually.
public static byte[] GetByteArray(List<MyModel> models)
{
var ms = new MemoryStream();
using var sw = new StreamWriter(ms);
foreach (var model in models)
{
sw.Write(model.Id + "," + model.Name);
sw.WriteLine();
}
// flush the writer, to make sure it is written to the stream.
sw.Flush();
return ms.ToArray();
}
You don't need to dispose the memory stream, because the StreamWriter takes ownership.
I don't like the construct that the streamwriter takes ownage of the memory stream. This is probably because there the streamwriter can also be used directly on a file. A constructor which has a file path as parameter. (so no stream parameter is needed)
StreamWriter leaveOpen constructor
If you writing List<MyModel> items as strings, you can simplify conversion by:
public static byte[] GetByteArray(List<MyModel> models) =>
Encoding.UTF8.GetBytes(string.Join(Environment.NewLine,
models.Select(model => $"{model.Id},{model.Name}")));
Or use third-party serializers, such from Newtonsoft.Json (example from here):
public static byte[] Serialize<T>(this T source)
{
var asString = JsonConvert.SerializeObject(source, SerializerSettings);
return Encoding.Unicode.GetBytes(asString);
}
public static T Deserialize<T>(this byte[] source)
{
var asString = Encoding.Unicode.GetString(source);
return JsonConvert.DeserializeObject<T>(asString);
}
As the others have mentioned you have to Flush the StreamWriter
This is what your function looks like:
public static byte[] GetByteArray(List<MyModel> models)
{
MemoryStream memoryStream = new MemoryStream();
try
{
StreamWriter streamWriter = new StreamWriter(memoryStream);
try
{
List<MyModel>.Enumerator enumerator = models.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
MyModel current = enumerator.Current;
streamWriter.Write(string.Concat(current.Id, ",", current.Name));
streamWriter.WriteLine();
}
}
finally
{
((IDisposable)enumerator).Dispose();
}
streamWriter.Dispose();
return memoryStream.ToArray();
}
finally
{
if (streamWriter != null)
{
((IDisposable)streamWriter).Dispose();
}
}
}
finally
{
if (memoryStream != null)
{
((IDisposable)memoryStream).Dispose();
}
}
}

Passing Stream to a Funtion and Leaving it Open

I am having trouble writing to stream. I think i understand why, but im not really following how to get around the issue. Please see example code below
The requirement that the caller of Writer in the example below manages the stream, not the Writer.
this is example code in the caller:
using (Stream stream1 = new MemoryStream())
using (Stream stream2 = new FileStream("ex.txt", FileMode.Create))
{
//example 1:
Writer writer1 = new Writer();
writer1.WriteToStream(stream1);
//example 2:
Writer writer2 = new Writer();
writer2.WriteToStream(stream2);
}
This is the Writer class: its supposed to leave stream open after its done with it.
public class Writer
{
public void WriteToStream(Stream destination)
{
Write(destination, "abc");
}
private void Write(Stream destination, string data)
{
Streamwriter sw = new StreamWriter(destination);
sw.Write(data);
}
}
in this setup, nothing shows up in MemoryStream or "ex.txt". I am guessing its because once you exit the Write method, StreamWriter is out of context, and stream goes with it before it gets a chance to be written to a file.
If I change the Write method to the example below, then i can get something to show up in the file, but the stream ends up closed, which goes against the requirement:
private void Write(Stream destination, string data)
{
using(Streamwriter sw = new StreamWriter(destination))
{
sw.Write(data);
}
}
So, how do I write a string to a stream (Memory of File), without closing the stream in the process. thank you
You are missing Stream Flush.
Following will solve the issue:
private void Write(Stream destination, string data)
{
var sw = new StreamWriter(destination);
sw.Write(data);
sw.Flush();
}
However, open streams are notorious.
The easiest way is probably to use the StreamWriter constructor that allows us to leave the underlying Stream open and set leaveOpento true.
private void Write(Stream destination, string data)
{
using (var sw = new StreamWriter(destination, Encoding.UTF8, 1024, leaveOpen: true))
{
sw.Write(data);
}
}

C# move to end of file with StreamWriter created from FileStream

I need to create a StreamWriter from a FileStream object and append some text to
the file. It is assumed that the FileStream object that is being used has been created with FileMode.OpenOrCreate and FileAccess.ReadWrite. I have:
using (FileStream fs = GetCurrentFileStream())
{
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine("StringToAppend");
sw.Flush();
}
However this just overwrites the file from the beginning. How do I move to the end of the file? Is there perhaps a way to change the FileMode to Append and FileAccess to Write after the FileStream has been created?
Edit: As mentioned above I need to do this using a FileStream object. The answers from Open existing file, append a single line assume that I can create a new StreamWriter from the file path which I don't have.
Edit 2: Added truncated version of GetCurrentFileStream().
public static FileStream GetCurrentFileStream()
{
String fileName = getFileName();
FileStream fs = OpenFileWhenAvailable(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}
public static FileStream OpenFileWhenAvailable(String fileName, FileMode fileMode, FileAccess fileAccess, FileShare fileShare)
{
int tries = 0;
int timeout = 10 * 1000;
while (true)
{
tries++;
try
{
return new FileStream(fileName, fileMode, fileAccess, fileShare);
}
catch (IOException ex)
{
if (tries * 100 > timeout)
{
return null;
}
else
{
System.Threading.Thread.Sleep(100);
}
}
}
}
GetCurrentFileStream is used in several different contexts, so changing the FileMode and FileAccess directly is not an option. I do not wish to make a separate version of GetCurrentFileStream just for this one case, which is why I'm asking if there is a way to jump to the end of the stream and append a string when the FileStream object has already been created.
If I understood correctly, you want to append your line to a created file:
using (FileStream fs = GetCurrentFileStream())
{
StreamWriter sw = new StreamWriter(fs, true);
sw.WriteLine("StringToAppend");
sw.Flush();
}
With this overload of the StreamWriter constructor you choose if you append the file, or overwrite it.
It will be really cool if you show your implementation of method GetCurrentStream():
using (FileStream fileStream = new FileStream(fileName,FileMode.Append, FileAccess.Write))
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine(something);
}
Update:
using (FileStream fs = GetCurrentFileStream())
{
StreamWriter sw = new StreamWriter(fs);
long endPoint=fs.Length;
// Set the stream position to the end of the file.
fs.Seek(endPoint, SeekOrigin.Begin);
sw.WriteLine("StringToAppend");
sw.Flush();
}
If you really really wanted to, you could pretty this up....
static int iMaxLogLength = 15000;
static int iTrimmedLogLength = -2000;
static public void writeToFile2(string strMessage, string strLogFileDirectory, int iLogLevel)
{
string strFile = strLogFileDirectory + "log.log";
try
{
FileInfo fi = new FileInfo(strFile);
Byte[] bytesRead = null;
if (fi.Length > iMaxLogLength)
{
using (BinaryReader br = new BinaryReader(File.Open(strFile, FileMode.Open)))
{
// Go to the end of the file and backup some
br.BaseStream.Seek(iTrimmedLogLength, SeekOrigin.End);
// Read that.
bytesRead = br.ReadBytes((-1 * iTrimmedLogLength));
}
}
byte[] newLine = System.Text.ASCIIEncoding.ASCII.GetBytes(Environment.NewLine);
FileStream fs = null;
if (fi.Length < iMaxLogLength)
fs = new FileStream(strFile, FileMode.Append, FileAccess.Write, FileShare.Read);
else
fs = new FileStream(strFile, FileMode.Create, FileAccess.Write, FileShare.Read);
using (fs)
{
if (bytesRead != null)
{
fs.Write(bytesRead, 0, bytesRead.Length);
fs.Write(newLine, 0, newLine.Length);
Byte[] lineBreak = Encoding.ASCII.GetBytes("### " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " *** *** *** New Log Start Position *** *** *** *** ###");
fs.Write(lineBreak, 0, lineBreak.Length);
fs.Write(newLine, 0, newLine.Length);
}
Byte[] sendBytes = Encoding.ASCII.GetBytes(strMessage);
fs.Write(sendBytes, 0, sendBytes.Length);
fs.Write(newLine, 0, newLine.Length);
}
}
catch (Exception ex)
{
; // Write to event or something
}
}

asp.net mvc generate data to download file

public ActionResult GetFile(string dateStr, string serverName, string foodName)
{
using (var memoStream = new MemoryStream(1024 * 5))
{
using (StreamWriter writer = new StreamWriter(memoStream))
{
var dataFilter = new CapacityDataFilter(dateStr, serverName, feedName);
dataFilter.FilterDataByServerAndFeed();
writer.WriteLine("Feed, StreamMin, TotalMsgNumber, TotalMsgSize, PeakRateMsgNumber, PeakRateMsgSize");
foreach (var element in dataFilter.DataInTheDay)
{
writer.WriteLine(string.Format("{0},{1},{2},{3},{4},{5}",
element.Feed, element.StreamMin,
element.TotalMsgNumber, element.TotalMsgSize,
element.PeakRateMsgNumber, element.PeakRateMsgSize));
}
return File(memoStream, "text/csv", fileName);
}
}
}
Exception Details: System.ObjectDisposedException: Cannot access a closed Stream.
this action doesn't work, how to make a download action?
"this action not work" is wrong way to explain problems...
But in this case you are lucky: you are trying to send incomplete data in memory stream that is seeked to the end, and as second issue stream will be disposed by the time File action actually executes.
The best fix is to move code outisde inner using and return new MemoryStream on buffer of old stream:
using (var memoStream = new MemoryStream(1024 * 5))
{
using (StreamWriter writer = new StreamWriter(memoStream))
{
...
}
return File(
new MemoryStream(memoStream.GetBuffer(), memoStream.length),
"text/csv", fileName);
}
Flush + Seek as proposed by armen.shimoon would work too in other cases when you are using stream immediately:
writer.Flush();
memoStream.Flush();
memoStream.Position = 0;
// do something with MemoryStream right here before either of `using`ends.

Categories