An Abstraction Layer for Temporary Data in .NET and Silverlight
Up until now, I’ve dealt with temporary data in my .NET applications using local files and FileInfo instances. This worked just fine – until I needed a solution that works under both .NET and Silverlight.
The problem: In Silverlight, you can’t just create a temporary file on the file system for security reasons. Instead, there’s the concept of isolated storage that provides you with the means to store data in files and directories. The API is very similar to working with the local file system, but it doesn’t use the FileInfo class. As a result, I needed to get rid of the (proprietary) concept of FileInfo in my temporary file handling and came up with a simple yet generic solution.
Here’s a sample usage of the API. This code transparently creates temporary storage, and works in both .NET and Silverlight:
private void Foo(ITempStreamFactory factory) { using (Stream stream = factory.CreateTempStream()) { //write to the stream, read from it } //once we get outside the "using" block, temporary data has been discarded }
The snippet above relies on a factory of type ITempStreamFactory that returns a simple Stream instance. I do not need to know anything about this returned stream – it’s the factory’s responsibility to return a Stream instance that will clean up after itself once it is being disposed.
In the snippet above, disposal happens implicitly through the using statement. But of course, the stream can also be disposed explicitly:
private Stream tempStream; private void CreateAndWrite(ITempStreamFactory factory) { //get stream tempStream = factory.CreateTempStream(); //write to the stream ... } private void ReadAndDispose() { //read data ... //close the temp stream, which removes the file from isolated storage tempStream.Dispose(); }
Factories
Factory implementations are very simple – they just need to create a Stream class that can be used. Here’s the interface:
/// <summary> /// Responsible for the creation of stream instances /// which can be used to read and write temporary data. /// </summary> public interface ITempStreamFactory { /// <summary> /// Creates a stream that automatically cleans up after itself. /// </summary> Stream CreateTempStream(); }
TempStream Base Class
So basically, the convention is that ITempStreamFactory returns streams that clean up after themselves. In order simplify the implementation of such streams, I created an abstract base class called TempStream. TempStream itself derives from Stream, and applies the Decorator pattern. All stream-related methods just forward requests to an underlying (decorated) stream. As an example, here’s the implementation of the Write method:
public override void Write(byte[] buffer, int offset, int count) { //everything is just forwarded to an underlying (decorated) stream DecoratedStream.Write(buffer, offset, count); }
However, besides just forwarding request to a decorated stream, the TempStream class provides two abstract methods that need to be implemented by deriving classes:
- The CreateTempDataStream method is invoked in order to create that decorated stream.
- The DiscardTempResources method is invoked during disposal. When invoked, implementing classes should clean up (e.g. delete temp files).
/// <summary> /// Creates the underlying stream. This method is being invoked on the first /// request of the <see cref="DecoratedStream"/> property. /// </summary> /// <returns>A temporary stream that supports seeking.</returns> protected abstract Stream CreateTempDataStream(); /// <summary> /// When invoked (which happens during disposal), implementing classes /// should clean up temporary resources. Note that at the time this /// method is being invoked, the <see cref="DecoratedStream"/> has /// already been disposed. /// </summary> protected abstract void DiscardTempResources();
Implementations for Local Files / Isolated Storage
Local File System
In order to handle temporary files on the local file system, I implemented the TempFileStream class that takes a reference to a temporary file. The CreateTempDataStream method opens a stream to that file, and the DiscardTempResources deletes the file once it’s no longer required.
This is a simplistic implementation for the sample – the one I’ll be using in production code also deals with a temporary root directory and so forth. But the concept is the same.
public class TempFileStream : TempStream { // The maintained temporary file. public FileInfo TempFile { get; private set; } public TempFileStream(FileInfo tempFile) { Ensure.ArgumentNotNull(tempFile, "tempFile"); TempFile = tempFile; } protected override Stream CreateTempDataStream() { //open stream to the local file return TempFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite,
FileShare.ReadWrite | FileShare.Delete); } protected override void DiscardTempResources() { //delete temp file TempFile.Refresh(); if (TempFile.Exists) TempFile.Delete(); } }
TempFileStream instances can be acquired via the TempFileStreamFactory, which implements the ITempStreamFactory interface. Here’s a simplistic implementation (the one in the attached project is a little smarter):
/// <summary> /// A factory implementation that returns /// <see cref="TempFileStream"/> instances which /// use local temporary files. /// </summary> public class TempFileStreamFactory : ITempStreamFactory { public Stream CreateTempStream() { FileInfo fi = new FileInfo(Path.GetTempFileName()); return new TempFileStream(fi); } }
Silverlight / Isolated Storage
In order to handle temporary files in the Silverlight sample, I implemented the IsolatedStorageTempStream class that creates and deletes a temporary file in isolated storage. The CreateTempDataStream method creates the stream using a GUID as a temporary name, and the DiscardTempResources override removes the data from isolated storage once it’s no longer required.
/// <summary> /// Provides temporary file management for Silverlight /// applications based on isolated storage. /// </summary> public class IsolatedStorageTempStream : TempStream { /// <summary> /// The name of the tempoarary file that is being created /// in isolated storage. /// </summary> public string TempFileName { get; private set; } public IsolatedStorageTempStream() { //use a GUID as the temporary file name TempFileName = String.Format("{0}.tmp", Guid.NewGuid()); } /// <summary> /// Creates the underlying stream. This method is being invoked on the first /// request of the <see cref="TempStream.DecoratedStream"/> property. /// </summary> /// <returns>A temporary stream that supports seeking.</returns> protected override Stream CreateTempDataStream() { IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); return store.CreateFile(TempFileName); } /// <summary> /// When invoked (which happens during disposal), implementing classes /// should clean up temporary resources. Note that at the time this /// method is being invoked, the <see cref="TempStream.DecoratedStream"/> has /// already been disposed. /// </summary> protected override void DiscardTempResources() { IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); store.DeleteFile(TempFileName); } }
In the sample, IsolatedStorageTempStream instances are being acquired via the TempStreamFactory, which implements the ITempStreamFactory interface. The implementation is trivial:
/// <summary> /// Factory that creates temporary streams in the Silverlight /// sample. /// </summary> public class TempStreamFactory : ITempStreamFactory { /// <summary> /// Gets a <see cref="Stream"/> that automatically cleans up /// after itself. /// </summary> /// <returns>A stream that allows to write and read temporary /// data.</returns> public Stream CreateTempStream() { return new IsolatedStorageTempStream(); } }
Nice!!!
Thank you!!!