1. Problem
You need to persist data on the end user's machine.
2. Solution
Use isolated storage to store data on the client.
3. How It Works
In some situations, you may
want to store data to the client's computer, such as user-specific
settings or application state information. However, it is not possible
to use the regular file system of the operating system from a web
browser application, because native file system operations require full
trust, but Web-based applications run in a partial-trust isolated
sandbox.
NOTE
Elevated OOB applications have much more access to the file system.
Isolated storage provides
a safe client-side storage area for partial-trust applications to
persist information on a per-user basis. In Silverlight, all I/O
operations are restricted to the isolated storage. Silverlight 4
includes the ability to run Silverlight applications OOB when online or
offline. Offline Silverlight applications can still access the same
isolated storage area as they can when running in the browser, so users
have seamless access to their data.
Besides storing settings,
isolated storage can be used to improve user experience as well as
reduce bandwidth by storing partially filled-out forms, so that the
form data can be reloaded when the user returns, even if, for example,
the user stored the data using Internet Explorer but accesses the
application later using Firefox.
The System.IO.IsolatedStorage namespace contains types for creating and using a virtual file system. Table 1 lists the classes available in this namespace.
Table 1. Classes Related to IsolatedStorage
Class | Description |
---|
IsolatedStorageException | Exception that is thrown when an isolated storage operation fails |
IsolatedStorageFile | Represents an isolated storage area containing files and directories |
IsolatedStorageFileStream | Represents a file within isolated storage |
IsolatedStorageSettings | Provides a Dictionary object that stores key-value pairs within isolated storage |
Isolated storage is not
unlimited. Administrators can set user quota restrictions that limit
the amount of data that can be stored in isolated storage, so it is not
suited for large amounts of data. The default size of isolated storage
for in-browser Silverlight applications is 1MB. The default size of
isolated storage for out-of-browser Silverlight applications at install
time (called detach) is 25MB.
NOTE
Isolated storage
is not encrypted, though developers can encrypt and decrypt files
stored in local storage if desired. Developers can also sign and
validate signatures using the SHA1 hash function.
The quota can be
increased further by the user through the UI thread, usually as a
result of a UI event handler. Otherwise, the quota cannot be increased
on a background thread or without user action. Isolated storage remains
intact even if the browser cache is cleared, but isolated storage can
be manually deleted by the user or application by using the File I/O
classes.
Applications can request more space by invoking the IsolatedStorageFile.IncreaseQuotaTo method in response to a user-initiated event, such as a mouse click or key press, as noted previously.
To work with isolated storage, first obtain an isolated store for the application using the IsolatedStorageFile.GetUserStoreForApplication method. This returns an IsolatedStorageFile object, which you can use to create directories using the CreateDirectory method and files using the CreateFile method. The CreateFile method returns an IsolatedStorageFileStream object. The IsolatedStorageFileStream class inherits from FileStream, so you can use the class with StreamReader and StreamWriter objects.
Another option is to use the IsolatedStorageSettings class, which is a Dictionary object that can be used to quickly store key/value pairs in isolated storage.
4. The Code
To test isolated storage, your sample does two things. It allows a user to store and update a setting using the IsolatedStorageSettings class and to save and reload form state between browser sessions. Figure 1 shows the UI with a mock form.
The Silverlight application shown in Figure 1 has a TextBox on the left with "Hi There Book Reader!" as a value. Any value entered in this TextBox is stored in the IsolatedStorageSettings dictionary object, which is a convenient place to store name/value pair settings or data. The UserControl.Loaded event handler pulls this setting out of the collection, and the ButtonUpdateSetting event handler stores the setting when the Button titled Update Setting is clicked.
The Save Form Data button
and the Load Form Data button both work with the sample form fields
located in the rounded green/silver area on the right side of the
application. The Save Form Data button concatenates the text from the
form data into a string, with each value separated by the pipe (|) symbol. The Load Form Data button reads in the data as a String and calls String.Split to separate the fields into an array of string values.
The SaveFormData_Click event stores the form data into isolated storage. In general, any data that is persisted into IsolatedStorage is persisted between browser sessions. The ReadFormData_Click event retrieves the data from the file created in isolated storage. Listings 1 and 2 show the code for the Silverlight application's MainPage class.
Listing 1. Recipe 3's MainPage.xaml File
<UserControl x:Class="Ch02_ProgrammingModel.Recipe2_3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="#FFFFFFFF">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.06*"/>
<ColumnDefinition Width="0.455*"/>
<ColumnDefinition Width="0.485*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.08*"/>
<RowDefinition Height="0.217*"/>
<RowDefinition Height="0.61*"/>
<RowDefinition Height="0.093*"/>
</Grid.RowDefinitions>
<Button HorizontalAlignment="Stretch" Margin="8 " VerticalAlignment="Stretch"
Grid.Column="1" Grid.Row="1" Content="Save Form Data"
Click="SaveFormData_Click"/>
<StackPanel HorizontalAlignment="Stretch" Margin="8,8,10,8" Grid.Column="1"
Grid.Row="2">
<TextBlock Height="Auto" Width="Auto" Text="Enter Setting Value"
TextWrapping="Wrap" Margin="4,4,4,4"/>
<TextBox Height="126" Width="Auto" Text="" TextWrapping="Wrap"
Margin="4,4,4,4" x:Name="settingTextData"/>
</StackPanel>
<Button HorizontalAlignment="Stretch" Margin="8" VerticalAlignment="Stretch"
Grid.Column="2" Grid.Row="1" Content="Load Form Data"
Click="ReadFormData_Click"/>
<Button HorizontalAlignment="Stretch" Margin="4,4,14,4"
VerticalAlignment="Stretch"
Grid.Column="1" Grid.Row="3" Content="Update Setting"
Click="ButtonUpdateSetting"/>
<Border Grid.Column="2" Grid.Row="2" Grid.RowSpan="2"
CornerRadius="10,10,10,10">
<Border.Background>
<LinearGradientBrush EndPoint="0.560000002384186,0.00300000002607703"
StartPoint="0.439999997615814,0.996999979019165">
<GradientStop Color="#FF586C57"/>
<GradientStop Color="#FFA3BDA3" Offset="0.536"/>
<GradientStop Color="#FF586C57" Offset="0.968999981880188"/>
</LinearGradientBrush>
</Border.Background>
<StackPanel Margin="4,4,4,4" x:Name="FormData">
<TextBlock Height="Auto" Width="Auto" Text="First Name:"
TextWrapping="Wrap" Margin="2,2,2,0"/>
<TextBox Height="Auto" Width="Auto" Text="" TextWrapping="Wrap" x:
Name="Field1" Margin="2,0,2,4"/>
<TextBlock Height="Auto" Width="Auto" Text="Last Name:"
TextWrapping="Wrap" Margin="2,4,2,0"/>
<TextBox Height="Auto" x:Name="Field2" Width="Auto" Text=""
TextWrapping="Wrap" Margin="2,0,2,4"/>
<TextBlock Height="Auto" Width="Auto" Text="Company:"
TextWrapping="Wrap" Margin="2,4,2,0"/>
<TextBox Height="Auto" x:Name="Field3" Width="Auto" Text=""
TextWrapping="Wrap" Margin="2,0,2,2"/>
<TextBlock Height="22.537" Width="182" Text="Title:"
TextWrapping="Wrap" Margin="2,4,2,0"/>
<TextBox Height="20.772" x:Name="Field4" Width="182" Text=""
TextWrapping="Wrap" Margin="2,0,2,2"/>
</StackPanel>
</Border>
</Grid>
</UserControl>
Listing 2 has the codebehind page for MainPage.xaml where the events are located. The code declares several class level variables such as settings that are used by the event handlers to load and save setting values to IsolatedStorage.
Listing 2. Recipe 3's MainPage.xaml.cs Class File
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
namespace Ch02_ProgrammingModel.Recipe2_3
{
public partial class MainPage : UserControl
{
private IsolatedStorageSettings settings =
IsolatedStorageSettings.ApplicationSettings;
private string setting = "MySettings";
private string FormDataFileName = "FormFields.data";
private string FormDataDirectory = "FormData";
public MainPage()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
try
{
if (settings.Keys.Count != 0)
{
settingTextData.Text = settings[setting].ToString();
}
}
catch (IsolatedStorageException ex)
{
settingTextData.Text = "Error saving setting: " + ex.Message;
}
}
private void SaveFormData_Click(object sender, RoutedEventArgs e)
{
try
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
//Use to control loop for finding correct number of textboxes
int TotalFields = 4;
StringBuilder formData = new StringBuilder(50);
for (int i = 1; i <= TotalFields; i++)
{
TextBox tb = FindName("Field" + i.ToString()) as TextBox;
if (tb != null)
formData.Append(tb.Text);
//If on last TextBox value, don't add "|" character to end of data
if (i != TotalFields)
formData.Append("|");
}
store.CreateDirectory(FormDataDirectory);
IsolatedStorageFileStream fileHandle =
store.CreateFile(System.IO.Path.Combine(
FormDataDirectory, FormDataFileName));
using (StreamWriter sw = new StreamWriter(fileHandle))
{
sw.WriteLine(formData);
sw.Flush();
sw.Close();
}
}
}
catch (IsolatedStorageException ex)
{
settingTextData.Text = "Error saving data: " + ex.Message;
}
}
private void ReadFormData_Click(object sender, RoutedEventArgs e)
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
//Load form data using private string values for directory and filename
string filePath =
System.IO.Path.Combine(FormDataDirectory, FormDataFileName);
//Check to see if file exists before proceeding
if (store.FileExists(filePath))
{
using (StreamReader sr = new StreamReader(
store.OpenFile(filePath, FileMode.Open, FileAccess.Read)))
{
string formData = sr.ReadLine();
//Split string based on separator used in SaveFormData method
string[] fieldValues = formData.Split('|');
for (int i = 1; i <= fieldValues.Count(); i++)
{
//Use the FindName method to loop through TextBoxes
TextBox tb = FindName("Field" + i.ToString()) as TextBox;
if (tb != null)
tb.Text = fieldValues[i − 1];
}
sr.Close();
}
}
}
}
private void ButtonUpdateSetting(object sender, RoutedEventArgs e)
{
try
{
settings[setting] = settingTextData.Text;
}
catch (IsolatedStorageException ex)
{
settingTextData.Text = "Error reading setting: " + ex.Message;
}
}
}
}