This blog post was authored by Douglas Laudenschlager, a Senior Content Developer on the Windows Phone Developer Content team.
– Adam
Windows Phone 8 introduces new APIs for file handling that are convergent with the APIs in Windows Store apps. These APIs are in the Windows.Storage and Windows.Storage.Streams namespaces. You can use them in place of the APIs from the System.IO.IsolatedStorage namespace that might be familiar to a Windows Phone 7 developer. When you switch to the Windows.Storage APIs, it’ll be easier to port your Windows Phone app to the Windows Store, and easier to upgrade your app to future versions of the Windows Phone operating system.
You can see all the methods and properties of these new namespaces here:
Many of the methods in these namespaces are asynchronous methods that require you to use the async and await keywords. For more info, see Asynchronous Programming with Async and Await (C# and Visual Basic).
Learn from the sample
The Windows Phone SDK documentation team recently published the Basic Storage Recipes sample. Download the sample to see working examples of the programming tasks described in this blog post.
Here’s the list of tasks demonstrated in the Basic Storage Recipes sample:
In addition to showing you how to work with files, the Basic Storage Recipes sample also demonstrates the following tasks:
- How to retrieve a directory tree recursively
- How to pass data from one page to the next without using the query string in the page URI
- How to persist application state when the app is deactivated
- How to call async methods successfully in the Application_Deactivated event handler
- How to use the PhotoChooserTask and the CameraCaptureTask.
The local folder is still “isolated storage”
In Windows Phone 8, the isolated area of storage reserved for each app is called the local folder. Only the app itself (and the operating system) can access the files you store in this folder. Here’s how you retrieve the local folder using the Windows.Storage APIs:
- StorageFolder localFolder = ApplicationData.Current.LocalFolder;
Here’s how you previously retrieved the local folder using the IsolatedStorage APIs:
- IsolatedStorageFile store =
- IsolatedStorageFile.GetUserStoreForApplication();
Creating new folders and files
To create a new subfolder in the local folder, call the StorageFolder.CreateFolderAsync method.
- StorageFolder newFolder =
- await localFolder.CreateFolderAsync(folderName,
- CreationCollisionOption.ReplaceExisting);
To create a new file in the local folder, call the StorageFolder.CreateFileAsync method.
- StorageFile newFile =
- await localFolder.CreateFileAsync(newFileName,
- CreationCollisionOption.ReplaceExisting);
Writing a text file
Use these methods to write a new text file in the local folder:
- Create the new file using the StorageFolder.CreateFileAsync method.
- Open the file using the StorageFile.OpenAsync method, which returns a stream.
- Open a DataWriter over the stream returned by OpenAsync.
- Write the text to the stream with the DataWriter.WriteString method.
- Flush and close the DataWriter using the StoreAsync method.
The sample method returns the path of the new text file. The sample app uses this return value to maintain a list of all the files it creates so it can delete them later.
- // Write a text file to the app’s local folder.
- public static async Task<string> WriteTextFile(string filename, string contents)
- {
- StorageFolder localFolder = ApplicationData.Current.LocalFolder;
- StorageFile textFile = await localFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
- using (IRandomAccessStream textStream = await textFile.OpenAsync(FileAccessMode.ReadWrite))
- {
- using (DataWriter textWriter = new DataWriter(textStream))
- {
- textWriter.WriteString(contents);
- await textWriter.StoreAsync();
- }
- }
- return textFile.Path;
- }
Reading a text file
Use these methods to read the contents of a text file in the local folder:
- Get the file using StorageFolder.GetFileAsync.
- Open the file for reading using StorageFile.OpenReadAsync, which returns a stream.
- Open a DataReader over the stream returned by OpenReadAsync.
- Load the contents of the stream using DataReader.LoadAsync.
- Retrieve the contents as a string using DataReader.ReadString.
The sample method returns the contents of the text file as a string. The sample app uses this return value to display the output on a new page.
- // Read the contents of a text file from the app’s local folder.
- public static async Task<string> ReadTextFile(string filename)
- {
- string contents;
- StorageFolder localFolder = ApplicationData.Current.LocalFolder;
- StorageFile textFile = await localFolder.GetFileAsync(filename);
- using (IRandomAccessStream textStream = await textFile.OpenReadAsync())
- {
- using (DataReader textReader = new DataReader(textStream))
- {
- uint textLength = (uint)textStream.Size;
- await textReader.LoadAsync(textLength);
- contents = textReader.ReadString(textLength);
- }
- }
- return contents;
- }
Copying a photo
Use these methods to create a new binary file from the stream returned by the PhotoChooserTask or the CameraCaptureTask.
- Create the new file using the StorageFolder.CreateFileAsync method.
- Open an output stream for writing using the StorageFile.OpenStreamForWriteAsync extension method.
- Copy the binary data from the input stream to the output stream using the System.IO.Stream.CopyToAsync method.
The sample method returns the path of the new binary file. The sample app uses this return value to maintain a list of all the files it creates so it can delete them later.
- // Save a photo to the app’s local folder.
- public static async Task<string> SavePhoto(Stream photoToSave, string fileName)
- {
- StorageFolder localFolder = ApplicationData.Current.LocalFolder;
- StorageFile photoFile = await localFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
- using (var photoOutputStream = await photoFile.OpenStreamForWriteAsync())
- {
- await photoToSave.CopyToAsync(photoOutputStream);
- }
- return photoFile.Path;
- }
Reading a directory tree recursively
The Basic Storage Recipes sample displays the contents of the app’s local folder after you run each task.
To build the directory tree recursively, the sample app uses the following two methods, which are contained in a static helper class. These methods store the directory tree in a StringBuilder named folderContents.
- private static StringBuilder folderContents;
- private const string FOLDER_PREFIX = “”;
- private const int PADDING_FACTOR = 3;
- private const char SPACE = ‘ ‘;
- // Begin recursive enumeration of files and folders.
- public static async Task<string> EnumerateFilesAndFolders(StorageFolder rootFolder)
- {
- // Initialize StringBuilder to contain output.
- folderContents = new StringBuilder();
- folderContents.AppendLine(FOLDER_PREFIX + rootFolder.Name);
- await ListFilesInFolder(rootFolder, 1);
- return folderContents.ToString();
- }
- // Continue recursive enumeration of files and folders.
- private static async Task ListFilesInFolder(StorageFolder folder, int indentationLevel)
- {
- string indentationPadding = String.Empty.PadRight(indentationLevel * PADDING_FACTOR, SPACE);
- // Get the subfolders in the current folder.
- var foldersInFolder = await folder.GetFoldersAsync();
- // Increase the indentation level of the output.
- int childIndentationLevel = indentationLevel + 1;
- // For each subfolder, call this method again recursively.
- foreach (StorageFolder currentChildFolder in foldersInFolder)
- {
- folderContents.AppendLine(indentationPadding + FOLDER_PREFIX + currentChildFolder.Name);
- await ListFilesInFolder(currentChildFolder, childIndentationLevel);
- }
- // Get the files in the current folder.
- var filesInFolder = await folder.GetFilesAsync();
- foreach (StorageFile currentFile in filesInFolder)
- {
- folderContents.AppendLine(indentationPadding + currentFile.Name);
- }
- }
Persisting application state in the Application_Deactivated event handler
If you call an async method directly to persist state in the Application_Deactivated event handler, the method never finishes its work. The trick is to run the task on a separate thread and wait for its completion. The following routine calls two helper methods that save the lists of folders and files created by the sample app when it’s deactivated. When the user activates the app again, the list of temporary files is still available for the Cleanup method.
- // Code to execute when the application is deactivated (sent to background)
- // This code will not execute when the application is closing
- private void Application_Deactivated(object sender, DeactivatedEventArgs e)
- {
- Task.Run(async () =>
- {
- await StateHelper.SaveList(foldersToDelete, FOLDER_LIST_NAME);
- }).Wait();
- Task.Run(async () =>
- {
- await StateHelper.SaveList(filesToDelete, FILE_LIST_NAME);
- }).Wait();
- }
System.IO is still useful
The Basic Storage Recipes sample app we refer to in this blog post contains several methods and properties from the System.IO namespace that are still useful:
- Extension methods such as OpenStreamForWriteAsync and OpenStreamForReadAsync
- The Directory.Exists and File.Exists methods and the FileNotFoundException
- The Path.Combine, Path.GetFilename, and Path.GetTempFileName methods
We hope you’ll find the Basic Storage Recipes sample useful as you adopt the Windows.Storage APIs in your apps for Windows Phone 8.