Passing Stream to a Funtion and Leaving it Open - c#

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

Related

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

FileResult throws System.ObjectDisposedException: Cannot access a closed file

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.

How to close stream before return in method?

I'm getting System.IO.IOException because my file being used by another process. Is it because of unclosed stream? If yes how can I close it?
public static ReportClass DeserializeRep(string FileWay)
{
Stream stream = File.Open(FileWay, FileMode.Open);
BinaryFormatter bformatter = new BinaryFormatter();
return (ReportClass)bformatter.Deserialize(stream);
}
var CurRep = RequestSerializer.DeserializeRep(paths[selected]);
You should use the using statement:
public static ReportClass DeserializeRep(string FileWay)
{
using (Stream stream = File.Open(FileWay, FileMode.Open))
{
BinaryFormatter bformatter = new BinaryFormatter();
return (ReportClass)bformatter.Deserialize(stream);
}
}
It should also be noted that the using statement automatically calls the Dispose method of any object that inherits from IDisposible, which in this case closes the connection and then disposes the object.
IDisposible Documentation can be found here.

unit testing a void method with StreamWriter as one of the parameters

I currently have a method like this
public void BusinessMethod(object value, StreamWriter sw)
{
//Calls a private method that converts the data in `value` to a custom formatted string variable `str`
string str = myPrivateMethod(value);
//write the string to stream
sw.Write(str);
}
I am trying to test this method using the approach mentioned here and have done exactly the same thing. However, my result string comes back as an empty string. I cannot change the method signature. How does one test a method like this? I am using Nunit for testing.
This is my test method
[Test]
public void My_Test()
{
MyPoco dto = new MyPoco ();
//set up the dto properties here
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream))
{
sut.BusinessMethod(dto, writer);
string result = Encoding.UTF8.GetString(stream.ToArray());
}
}
You need to Close/Flush/Dispose writer so it actually commits changes to stream:
using (var stream = new MemoryStream())
{
using (var writer = new StreamWriter(stream))
{
sut.BusinessMethod(dto, writer);
}
// moved outside of inner using to ensure writer stored content to stream
string result = Encoding.UTF8.GetString(stream.ToArray());
}

Redirect console output to readable memory not to file?

I was using this code to redirect my console output to file and then read and display it. I want to go away from using files because I'm polluting my folders with those console files. How can I do this in memory ? I don't want any files to pollute the system. Maybe I'm trying something weird here. I just want 1 thread to read the console output of the very same application:
1 application
multiple threads write to console
1 thread reads from console
My working file code:
private StreamWriter currentOut = null;
private void RedirectConsole()
{
currentOut = new StreamWriter(new FileStream(filename,
FileMode.Create, FileAccess.Write, FileShare.Read));
currentOut.AutoFlush = true;
Console.SetOut(currentOut);
ThreadPool.QueueUserWorkItem(o => { Listen(); });
}
private void Listen()
{
StreamReader fileIn = new StreamReader(new FileStream(filename,
FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
while (true)
{
try
{
if (!fileIn.EndOfStream)
{
string a = fileIn.ReadLine();
MessageBox.Show(a);
}
Thread.Sleep(25);
}
catch { }
}
}
This seems to be what I want. But I'm unable to implement that (help?). File is like a buffer. You write to it from one end and read from another. I need the same in memory.
Try:
private StreamWriter currentOut = null;
private MemoryStream ms = new MemoryStream();
private void RedirectConsole()
{
currentOut = new StreamWriter(ms);
currentOut.AutoFlush = true;
Console.SetOut(currentOut);
ThreadPool.QueueUserWorkItem(o => { Listen(); });
}
private void Listen()
{
StreamReader fileIn = new StreamReader(ms);
// ...
}
The problem with using MemoryStream is that the read position advances with the write position. Pipes (System.IO.Pipes namespace) are a better choice for use as temporary buffers where the read position needs to advance independent of the write position. Admittedly, this more or less does exactly what your working solution does, though it removes the need to implement the buffer yourself.
class ConsoleRedirector : IDisposable
{
private TextWriter originalOut = Console.Out;
private AnonymousPipeServerStream consoleOutServerPipe;
private StreamWriter currentOut;
public ConsoleRedirector()
{
this.consoleOutServerPipe = new AnonymousPipeServerStream(PipeDirection.Out);
this.currentOut = new StreamWriter(this.consoleOutServerPipe);
this.currentOut.AutoFlush = true;
Console.SetOut(this.currentOut);
ThreadPool.QueueUserWorkItem(o => { this.Listen(); });
}
private void Listen()
{
AnonymousPipeClientStream consoleOutClientPipe = new AnonymousPipeClientStream(PipeDirection.In, this.consoleOutServerPipe.ClientSafePipeHandle);
using (StreamReader fileIn = new StreamReader(consoleOutClientPipe))
{
// ...
}
}
public void Dispose()
{
this.currentOut.Dispose();
Console.SetOut(this.originalOut);
}
}
I ended up writing a derived stream class and replaced the FileStream with my own stream. I probably should have avoided that. But since I couldn't find a working solution, it was also a good practice. Something like this:
public class MyStream: Stream
{
private byte[] internalBuffer = new byte[4096];
// ...
public override int Read(byte[] buffer, int offset, int count)
{
// used by StreamReader
}
public override void Write(byte[] buffer, int offset, int count)
{
// used by StreamWriter
}
}
override all the other stuff, handle multi-threading while enlarging internalBuffer and disposing passed data.

Categories