Say I want to define a TempFileStream class that creates a temporary file using Path.GetTempFileName() method. A temporary file must be deleted when TempFileStream\'s object
Basically according to TempFileStream logic you always use just created file with unique name (that is what Path.GetTempFileName does) and you always delete it after its use. So there is no need to provide constructor that accepts FileMode as you always use it in the same mode.
I know this is an older thread, but here's an alternate solution. I started to implement the TempFileStream, but I wanted more concurrency. My use-case involves exporting [potentially MBs of] database results to a CSV file via MVC. I want to begin downloading to the client as soon as data are available from the database query rather than wait for a potentially large temp file to be written before I start downloading.
In this solution, I launch the query in a separate thread which fills an AnonymousPipeStream. The main thread can then slurp the data from the other end of the pipe as its available. It uses .Net 4 Tasks.
Hope someone else finds this useful.
-Rob
Controller method:
public FileResult ResultExport ( ReportOptions options )
{
ResultExport rpt = new ResultExport( options );
HttpContext.Response.BufferOutput = false;
return File( rpt.Export(), "text/csv", "results.csv" );
}
Model:
public ResultExport
{
private AnonymousPipeServerStream WriteStream = null;
public Stream Export()
{
//
// We'll fire off the database query in a background
// thread. It will write data to one end of the pipe. We'll return the reader
// end of that pipe to our caller.
//
WriteStream = new AnonymousPipeServerStream( PipeDirection.Out );
AnonymousPipeClientStream reader = new AnonymousPipeClientStream( PipeDirection.In, WriteStream.ClientSafePipeHandle );
//
// Call Execute() in a background thread.
//
Task.Factory.StartNew( () => Execute() );
//
// While Execute() is filling the pipe with data,
// return the reader end of the pipe to our caller.
//
return reader;
}
private void Execute ()
{
//
// WriteStream should only by populated by Export()
//
if( WriteStream != null )
{
using ( StreamWriter sw = new StreamWriter( WriteStream, Encoding.UTF8, 4096 ) )
{
//
// Shove data into the StreamWriter as we get it from the database
//
foreach ( string line in ExportCore() )
{
// Each line is a comma-delimited set of values
sw.WriteLine( line );
}
}
}
}
}
Try this one instead:
public class TempFileStream : FileStream
{
public TempFileStream()
: base(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose) { }
public TempFileStream(FileAccess access)
: base(Path.GetTempFileName(), FileMode.Create, access, FileShare.Read, 4096, FileOptions.DeleteOnClose) { }
public TempFileStream(FileAccess access, FileShare share)
: base(Path.GetTempFileName(), FileMode.Create, access, share, 4096, FileOptions.DeleteOnClose) { }
public TempFileStream(FileAccess access, FileShare share, int bufferSize)
: base(Path.GetTempFileName(), FileMode.Create, access, share, bufferSize, FileOptions.DeleteOnClose) { }
}
The FileOptions.DeleteOnClose option will ensure that the OS deletes the temporary file automatically when you close out the file. No need for a special Dispose method, because it's all taken care of for you.
This is an interesting idea, but there's something about this design that troubles me. Forgive me if you've already addressed this in your design. But if your design is just a simple wrapper around FileStream
, there's a subtle but, I think, significant problem.
If you're deleting the file when the stream is closed, that means that the only way to actually use the data in the file is if the FileAccess
is ReadWrite
. Correct? In other words, you'll be using the file with code that looks like this:
using (TempFileStream t as new TempFileStream())
{
WriteDataToTempFile(t);
t.Seek(0, SeekOrigin.Begin);
ReadDataFromTempFile(t);
}
The problem I see is that ReadDataFromTempFile
is expecting the file to be opened for read access, not read/write access. And this opens the door for some bugs that I think will be very hard to find. Consider code like this:
using (TempFileStream t as new TempFileStream())
{
MyClass o = new MyClass(o);
o.TempStream = t;
o.ProduceOutput();
t.Seek(0, SeekOrigin.Begin);
o.ProcessOutput();
}
...when compared with this:
MyClass o = new MyClass();
string n = Path.GetTempFileName();
using (FileStream s = new FileStream(n, FileMode.Create, FileAccess.Write))
{
o.TempStream = t;
o.ProduceOutput();
}
using (FileStream s = new FileStream(n, FileMode.Open, FileAccess.Read))
{
o.TempStream = t;
o.ProcessOutput();
}
File.Delete(n);
Sure, the first method is shorter than the second. But the second method will throw an exception if ProcessOutput
calls a method that writes to TempStream
. (Or sets a property whose set accessor raises an event whose event handler dispatches a call to a method that writes to TempStream
, which how this problem will probably end up happening.) The first one will just produce unexpected results for no apparent reason.
You can get around this, I think, by having your TempFileStream
class open the underlying FileStream
using FileAccess.Write
. Then implement a Rewind
method that closes this FileStream
and creates a new one that uses FileAccess.Read
. If you do that, any method that tries to write to the file while it's opened for read access (or vice versa) will at least throw an exception.
base.Dispose(disposing); // disposes the base stream so the file is no longer used
if (disposing)
File.Delete(m_TempFileName); // deletes the file
You should add proper exception handling for File.Delete if you need to.