Saving and Loading Data

October 21, 2011

The data that your application displays will typically come from an external data source. Your application will need to save and load this data in some way.

You Will Learn

  • What types of data are supported on Windows Phone.
  • How to use a data access class to save and load data.
  • How to use long-term storage on Windows Phone.

Applies To

  • Silverlight for Windows Phone OS 7.1

    Gg680266.5f36eb1d-03c2-41cb-b719-a7e71c44fd25(en-us,PandP.11).png

Types of Data

Windows Phone supports different types of data. The data might be page-level or application-level state data, user-input data, static data, or data retrieved from the web. Additionally, it might be read-only, temporary for the current application session, or persisted in storage for all application sessions. Data in your application can exist in various locations. Your application can load data from the following locations and save data to all but the first location.

Data Location

Description

Examples

Resource and content files

Read-only files included with your application package. Resource files are loaded automatically when the DLLs that contain them are loaded. Content files are loaded when you access them.

A list of state capitals or a background image.

In-memory state dictionaries

Temporary storage, which is useful for transient state data while an application is deactivated. If new applications are launched after an application has been made dormant, and these applications require more memory than is available to provide a good user experience, the operating system will begin to tombstone dormant applications to free up memory. For more information, see Restoring Your Application After Deactivation.

The current control that has the focus.

Isolated storage

Storage that represents the file system of the phone, which is useful for the long-term storage of data between application sessions. Isolated storage is slow, but reliable.

A user-selected background color.

Remote storage

Storage in the cloud or on the web, which is useful for sharing data among multiple applications or application instances. Remote storage is slow, asynchronous, and sometimes unavailable, but provides the most flexibility. Remote storage is a large topic that is outside the scope of this documentation.

A web service that returns a list of wines.

Local Database

Storage for large amounts of relational data. The database file resides in isolated storage. For more information, see Local Database for Windows Phone.

A data set that populates multiple tables with relationships.

As your application starts and runs, there are different points at which you can save and load data. For example, isolated storage and remote storage are slow, so you should access them in the OnNavigatedTo method override of the page and not in Launching and Activated event handlers.

Note

If your save and load operations are time consuming, you should save the data as early as possible and load it as late as possible. However, you should avoid loading data in the Launching and Activated event handlers.

For more information about working with data on the phone, see the Getting Data into Windows Phone QuickStart.

Using a Data Access Class

It is useful to encapsulate the data storage and retrieval code in a data access class. This class can load the data on the first access and provide a cached copy on each subsequent access.

The Fuel Tracker application includes a data access class named CarDataStore that encapsulates all the required data storage and retrieval code. It includes a Car property and several methods for interacting with the data.

Gg680266.baad9f61-109f-4f96-8bd3-476812158672(en-us,PandP.11).png

Each page of the Fuel Tracker application displays data from a single Car object that the application must retrieve from storage. The static CarDataStore class makes the data available through the static Car property, as shown in the following code.

private static Car _car;

public static Car Car 
{ 
    get
    {
        if (_car == null)
        {
            // Initialize the car field using data retrieved from storage
            // or create a new Car object if there is no data in storage.
            // (The code for doing this is shown and described later.)
        }
        return _car;
    }
    set
    {
        car = value;
        NotifyCarUpdated();
    }
}
Private Shared _car As Car

Public Shared Property Car As Car
    Get
        If _car Is Nothing Then
            ' Initialize the car field using data retrieved from storage
            ' or create a new Car object if there is no data in storage.
            ' (The code for doing this is shown and described later.)
        End If
        Return _car
    End Get
    Set(value As Car)
        _car = value
        RaiseEvent CarUpdated()
    End Set
End Property

This code enables each page's OnNavigatedTo method override to retrieve the property value without regard for whether the data has already been loaded. For example, as described in Displaying Data with Data Binding, the FillupPage class sets the DataContext property of a UI element to the CarDataStore.Car property. This takes place in the InitializePageState method , which is called from the OnNavigatedTo method override, as shown in the following code.

private void InitializePageState()
{
    CarHeader.DataContext = CarDataStore.Car;

    // ... other code ...
}
Private Sub InitializePageState()
    CarHeader.DataContext = CarDataStore.Car

       ' ... other code ...
End Sub

Using Isolated Storage

Windows Phone applications can use isolated storage to store data locally on the phone. There are three ways an application can store data:

  1. Settings: store data in a state dictionary by using the IsolatedStorageSettings class
  2. Files and folders: store files and folders by using the IsolatedStorageFile class
  3. Relational data: store relational data in a local database by using LINQ to SQL

Fuel Tracker uses the IsolatedStorageSettings class to store serializble data of the Car object and the car picture is stored as a file by using the IsolatedStorageFile class. Isolated storage is analogous to the desktop file system. However, each application can access only its own part of isolated storage, and has no access to the storage used by any other application.

Tip

Even if you save and retrieve data externally using web services, you should cache the data on the phone for use when the network is unavailable.

Isolated storage is slower than the in-memory state dictionaries, so you should use it only for data that requires potentially long-term storage and for non-serializable objects, which are not supported by the state dictionaries. Most classes are serializable by default, but some are not, and require special handling, as described later. An example of a non-serializable object is an ImageSource object, which represents a binary picture file, such as a JPG.

You should carefully consider where you put your isolated storage code in order to minimize the impact of any delays. For example, if you have a lot of application data to store, you should avoid saving it all at once upon application exit, and instead save it incrementally. In general, you should save data to isolated storage as soon as it becomes available, or as early as possible.

Tip

Unlike the desktop version of Silverlight, on the phone, there is no limit to the amount of isolated storage space that an application can use. However, phones have significantly less overall storage capacity than desktop computers, so you should use as little space as possible.

The isolated storage state dictionary is easy to use. You simply access it through the static IsolatedStorageSettings.ApplicationSettings property, and then add, remove, or change values as with a normal dictionary. The dictionary automatically handles the work of retrieving and storing the values to isolated storage at appropriate times, although you can override this behavior.

To use the isolated storage to save and retrieve files, use the IsolatedStorageFile class, which represents the virtual file system interface. You get access to the storage by calling the GetUserStoreForApplication method. Once you have access to the store, you can call methods to open, create, and delete files and folders.

For more information about isolated storage, see the Isolated Storage QuickStart, Isolated Storage for Windows Phone, and Isolated Storage for Silverlight.

Saving Data in Isolated Storage

To save a serializable object to isolated storage using a dictionary, you just assign the object to IsolatedStorageSettings.ApplicationSettings and specify a key. Then you call the IsolatedStorageSettings.Save method. The Save call is not required because the application will automatically commit any stored values when the application exits. However, the Save method is useful so that you can incur the delay at a time of your choosing.

The following code example demonstrates how to save the Car object (excluding its non-serializable Picture property) in a dictionary. The CarDataStore.SaveCar method assigns the Car property value directly to the IsolatedStorageSettings.ApplicationSettings dictionary using the CAR_KEY constant. The value is committed to isolated storage when the IsolatedStorageSettings.Save method is called on the following line.

private const string CAR_PHOTO_FILE_NAME = "CarPhoto.jpg";
private const string CAR_KEY = "FuelTracker.Car";
private static readonly IsolatedStorageSettings appSettings =
    IsolatedStorageSettings.ApplicationSettings;

public static void SaveCar(Action errorCallback)
{
    try
    {
        appSettings[CAR_KEY] = Car;
        appSettings.Save();
        DeleteTempCarPhoto();
        SaveCarPhoto(CAR_PHOTO_FILE_NAME, Car.Picture, errorCallback);
        NotifyCarUpdated();
    }
    catch (IsolatedStorageException)
    {
        errorCallback();
    }
}
Private Const CAR_PHOTO_FILE_NAME As String = "CarPhoto.jpg"
Private Const CAR_KEY As String = "FuelTracker.Car"
Private Shared appSettings As IsolatedStorageSettings =
    IsolatedStorageSettings.ApplicationSettings

Public Shared Sub SaveCar(ByVal errorCallback As Action)
    Try
        appSettings(CAR_KEY) = Car
        appSettings.Save()
        DeleteTempCarPhoto()
        SaveCarPhoto(CAR_PHOTO_FILE_NAME, Car.Picture, errorCallback)
        RaiseEvent CarUpdated()
    Catch ex As IsolatedStorageException
        errorCallback()
    End Try
End Sub

It is possible that there is no isolated storage space available. In this case, the Save method call will throw an exception. It is important to handle this exception, but it doesn't make sense to do so in the data access layer. Instead, you will typically want to display a warning to the user. For this reason, the SaveCar method accepts an Action delegate that it calls when an exception occurs. This enables the calling code to handle the error in its own way without needing its own try/catch block or any dependency on a particular exception type. This technique is described in more detail in Validating Data Entry Input.

Gg680266.note(en-us,PandP.11).gifCertification Requirement:
Your application should handle exceptions raised by the .NET Framework. For details, see section 5.1 of Technical Certification Requirements.

The Car.Picture property represents a picture of the car and is a BitmapImage type. Since the BitmapImage type is not serializable, the Car.Picture property cannot be saved in the IsolatedStorageSettings.ApplicationSettings dictionary, so the picture is treated separately. However, you must explicitly exclude the Picture property from serialization or the Save method call will always throw an exception. To exclude a property from serialization, you apply the IgnoreDataMemberAttribute to it, as shown in the following code examples from the Car class.

[System.Runtime.Serialization.IgnoreDataMemberAttribute]
public BitmapImage Picture
{
    get { return _picture; }
    set
    {
        if (_picture == value) return;
        _picture = value;
        NotifyPropertyChanged("Picture");
    }
}
<System.Runtime.Serialization.IgnoreDataMemberAttribute()> _
Public Property Picture As BitmapImage
    Get
        Return _picture
    End Get
    Set(value As BitmapImage)
        If (_picture Is Nothing AndAlso value Is Nothing) OrElse
            (_picture IsNot Nothing AndAlso
             _picture.Equals(value)) Then Return
        _picture = value
        NotifyPropertyChanged("Picture")
    End Set
End Property

To save a file to isolated storage, you typically use the IsolatedStorageFile.CreateFile or IsolatedStorageFile.OpenFile methods. There are methods on IsolatedStorageFile to check if a file exists and to create directories.

Since the Car.Picture property is not serializable, it is saved as a file in isolated storage. The following code shows the SaveCarPhoto method, which saves the car picture as a file in a directory in isolated storage. The OpenFile method is used to create a new file. Note the use of the bitmap.SaveJpeg method. This helper method simplifies the steps to convert a BitmapImage into a Stream.

private const string CAR_PHOTO_DIR_NAME = "FuelTracker";

private static void SaveCarPhoto(string fileName, BitmapImage carPicture,
    Action errorCallback)
{
    if (carPicture == null) return;
    try
    {
        using (var store = IsolatedStorageFile.GetUserStoreForApplication())
        {
            var bitmap = new WriteableBitmap(carPicture);
            var path = Path.Combine(CAR_PHOTO_DIR_NAME, fileName);

            if (!store.DirectoryExists(CAR_PHOTO_DIR_NAME))
            {
                store.CreateDirectory(CAR_PHOTO_DIR_NAME);
            }

            using (var stream = store.OpenFile(path, FileMode.Create))
            {
                bitmap.SaveJpeg(stream,
                    bitmap.PixelWidth, bitmap.PixelHeight, 0, 100);
            }
        }
    }
    catch (IsolatedStorageException)
    {
        errorCallback();
    }
}
Private Const CAR_PHOTO_DIR_NAME As String = "FuelTracker"

Private Shared Sub SaveCarPhoto(ByVal fileName As String,
    ByVal carPicture As BitmapImage, ByVal errorCallback As Action)

    If carPicture Is Nothing Then Return

    Try
        Using store = IsolatedStorageFile.GetUserStoreForApplication

            Dim bitmap = New WriteableBitmap(carPicture)
            Dim path = System.IO.Path.Combine(CAR_PHOTO_DIR_NAME, fileName)

            If Not store.DirectoryExists(CAR_PHOTO_DIR_NAME) Then
                store.CreateDirectory(CAR_PHOTO_DIR_NAME)
            End If

            Using stream = store.OpenFile(path, FileMode.Create)
                bitmap.SaveJpeg(stream,
                    bitmap.PixelWidth, bitmap.PixelHeight, 0, 100)
            End Using

        End Using
    Catch ex As IsolatedStorageException
        errorCallback()
    End Try

End Sub

The following illustration shows conceptually how the Car object is saved in isolated storage.

Gg680266.e2e071a1-1973-411d-a193-6a2a614a8f66(en-us,PandP.11).png

Reading Data from Isolated Storage

To read an object from a dictionary in isolated storage, you just specify the key of the object that you want. The following code demonstrates how to retrieve values from a dictionary in isolated storage. This code revisits the CarDataStore.Car property source code from the previous Using a Data Access Class section, but includes the storage details this time.

private static Car _car;

public static Car Car 
{ 
    get
    {
        if (_car == null)
        {
            if (appSettings.Contains(CAR_KEY))
            {
                _car = (Car)appSettings[CAR_KEY];
                _car.Picture = GetCarPhoto(CAR_PHOTO_FILE_NAME);
            }
            else
            {
                _car = new Car()
                {
                    FillupHistory = new ObservableCollection<Fillup>()
                };
            }
        }
        return _car;
    }   
    set
    {
      _car = value;
      NotifyCarUpdated();
    }
}
Private Shared _car As Car

Public Shared Property Car As Car
    Get
        If _car Is Nothing Then
            If appSettings.Contains(CAR_KEY) Then
                _car = appSettings(CAR_KEY)
                _car.Picture = GetCarPhoto(CAR_PHOTO_FILE_NAME)
            Else
                _car = New Car With {
                    .FillupHistory = New ObservableCollection(Of Fillup)
                }
            End If
        End If
        Return _car
    End Get
    Set(value As Car)
     _car = value
     RaiseEvent CarUpdated()
    End Set
End Property

In this code, if the Car property does not yet have a value, it checks whether the settings dictionary contains a value for the key specified by the CAR_KEY constant. (Using a constant enables you to select the value with IntelliSense, thereby avoiding the possibility of typos.) If there is no value present, a new Car object is instantiated and its FillupHistory property is initialized. Otherwise, the Car is retrieved from the dictionary. Note, however, that the Car.Picture property is of type BitmapImage, which is not serializable. Because the IsolatedStorageSettings dictionary supports only serializable values, the Picture property must be handled separately.

To read a file in isolated storage, you typically use the IsolatedStorageFile.OpenFile method. The following code shows the GetCarPhoto method in Fuel Tracker to get the picture from isolated storage.

private static BitmapImage GetCarPhoto(string fileName)
{
    using (var store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        string path = Path.Combine(CAR_PHOTO_DIR_NAME, fileName);

        if (!store.FileExists(path)) return null;

        using (var stream = store.OpenFile(path, FileMode.Open))
        {
            var image = new BitmapImage();
            image.SetSource(stream);
            return image;
        }
    }
}
Private Shared Function GetCarPhoto(ByVal fileName As String) As BitmapImage

    Using store = IsolatedStorageFile.GetUserStoreForApplication

        Dim path As String = System.IO.Path.Combine(CAR_PHOTO_DIR_NAME, fileName)
        If Not store.FileExists(path) Then Return Nothing

        Using stream = store.OpenFile(path, FileMode.Open)
            Dim image = New BitmapImage
            image.SetSource(stream)
            Return image
        End Using

    End Using

End Function

Deleting Data from Isolated Storage

To delete values from the state dictionary, you call the IsolatedStorageSettings.Remove method and specify the key. Then you call the IsolatedStorageSettings.Save method to commit the change. To delete a file from isolated storage, you call the IsolatedStorageFile.DeleteFile method.

The following CarDataStore.DeleteCar method demonstrates how to delete a value from the state dictionary and delete a file.

public static void DeleteCar()
{
    appSettings.Remove(CAR_KEY);
    appSettings.Save();
    Car = null;
    DeleteCarPhoto();
    DeleteTempCarPhoto();
    NotifyCarUpdated();
}
Public Shared Sub DeleteCar()
    appSettings.Remove(CAR_KEY)
    appSettings.Save()
    Car = Nothing
    DeleteCarPhoto()
    DeleteTempCarPhoto()
    RaiseEvent CarUpdated()
End Sub

The CarDataStore.DeleteCar method first removes the CAR_KEY entry from the ApplicationSettings dictionary, and Save is called to commit the change. Then, the Car property is set to null so that it can be reinitialized if it is accessed again later. This method also uses the IsolatedStorageFile class to delete the car photo. Finally, the NotifyCarUpdated method sends a notification that the Car property has changed.

Gg680266.note(en-us,PandP.11).gifDesign Guideline:
Supply a Cancel button for actions that overwrite or delete data or are irreversible.

Next Topic | Previous Topic | Home