Silverlight and Local Storage

All applications benefit from local storage


May 26, 2008
URL:http://www.drdobbs.com/web-development/silverlight-and-local-storage/208300036

Silverlight applications are something in between traditional Web applications and smart clients. A Silverlight application is more powerful than a Web application because of its support for managed code and its embedded .NET common language runtime. Compared to a smart client application, instead, a Silverlight-based solution is still a partial trust application with, among other things, limited access to URLs and the local filesystem.

Nearly all applications, on nearly any platforms, would greatly benefit from local data storage. Notice that I'm not talking about storing data to a DBMS on some server. With local storage, I mean the application's ability to save some data on the client machine. This has never been an issue for desktop applications which have full access to the local disk. For Web applications, instead, it has always been a mission-impossible task. For security reasons, any code hosted by Web pages — typically, JavaScript code — can't just access the filesystem of the local machine. For years, Web page developers resorted to cookies, or server-side storage, to save some user-specific information.

As far as local storage is concerned, Silverlight confirms to be a platform sitting in between the Web and desktop. A Silverlight application can't freely perform I/O operations on the user's hard disk because those are considered critical from a security perspective. However, a Silverlight application takes advantage of a special and constrained programming interface that permits disk access only under certain conditions. This .NET Framework API is known as "isolated storage" and Silverlight fully supports it.

Why Is Local Storage Important?

What kind of content should an application store in the user's local disk? While it mostly depends on the particular application, a couple of categories of data can be easily identified. First off, applications may need to save application settings and everything that can be filed under that tag. Application settings include user preferences, configuration, extra features installed, and also user-specific data that are helpful and useful to the application. For example, you could save in the local storage the draft of a form so that users could come back later and fill out the rest of the form without losing any information.

Second, applications may need to temporarily save any data that the user is still working on and that has not been committed yet. The user will eventually submit this work to the server, but until then data has to be maintained on the client and updated as needed.

These two broad categories of data going into the local storage of a Silverlight application have significant differences as far their size is concerned. Application settings are usually short pieces of data limited to just a few KBs. Working data, instead, is usually a much larger chunk of data, even in the order of MBs, that contain text or binary data representing a piece of work.

Nearly all types of application may benefit from persistent settings; mostly occasionally connected applications, though, may benefit of client-side persistence of working data.

However, for both types of applications Silverlight is a very good and promising presentation layer. That's why you find support for local storage in Silverlight 2.0.

Isolated Storage in Silverlight 2.0

In the .NET Framework, isolated storage is a storage mechanism that enables partially trusted applications to save data on the local machine without violating any security policies set on the computer. Isolated storage is around since the first version of the .NET Framework and is especially useful for downloaded, partially trusted components that are not usually given access to the standard I/O mechanisms. These same applications, though, are usually granted the right to use isolated storage. In this way, applications coming from potentially untrusted sources can still do some disk I/O, albeit in a controlled way.

Silverlight has its own implementation of the isolated storage feature and doesn't directly rely on the bits provided with the .NET Framework. This is not surprising as Silverlight is not a desktop platform and doesn't rely on a full edition of the .NET Framework installed on the client machine. A compatible subset of the .NET isolated storage subsystem is supported in Silverlight, but don't be too concerned — reading, writing and deleting files and directories in a virtual and application specific filesystem are all supported features. Can you ask for more?

The entry point in the Silverlight isolated storage subsystem is the IsolatedStorageFile class and in particular its static method GetUserStoreForApplication. The method gets you a token that can be used to perform any supported operation on the virtual filesystem.

using (IsolatedStorageFile iso = 
       IsolatedStorageFile.GetUserStoreForApplication()) 
{
   :
}

The token you get through the GetUserStoreForApplication method is ultimately an aptly created instance of the IsolatedStorageFile class properly initialized to track which part of the physical filesystem it has to work on.

The root of the isolated storage filesystem is located in a hidden folder within the subtree reserved to the current user. If you're using Windows Vista, the hidden isolated storage folder lives under the Users directory. If you're using Windows 2003 Server, it will then be located under the Documents and Settings folder for the current user. Each Silverlight application has its own virtual filesystem that is completely separated from the filesystem visible to other applications. A Silverlight application can't just navigate out of its personal filesystem. File names are always intended to be relative to the filesystem meaning that you can't use absolute paths that include drive information. At the same time, any relative path that includes ellipsis \..\..\ is not allowed.

When the GetUserStoreForApplication method is invoked it first checks whether the proper subtree exists and, if not, it will create it. As an application developer, you only have to worry about your own files and directories. The system guarantees that the isolated storage infrastructure is always up and running for you.

Working with Files and Directories

The IsolatedStorageFile class features a number of methods to perform CRUD and lookup operations on files and directories. Table 1 lists the most important methods.

Silverlight and the Local Storage

Method name

Description

CreateFile

Creates a file with the specified name.

CreateDirectory

Creates a directory with the specified name. To create subdirectories you specify a combined name that includes the parent directories.

DeleteFile

Deletes the specified file. If the file doesnt exist you will receive an exception of type IsolatedStorageException.

DeleteDirectory

Deletes the specified directory, provided that the directory exists and is empty. In any other case, you will receive an exception of type IsolatedStorageException.

DirectoryExists

Indicates whether a directory with the specified name exists.

FileExists

Indicates whether a file with the specified name exists.

GetDirectoryNames

Enumerates all the directories in the current store that match the specified search criteria.

GetFileNames

Enumerates all the files in the current store that match the specified search criteria.

OpenFile

Opens the specified file and returns a stream for it. If the file doesnt exist, you will receive an exception of type IsolatedStorageException.

Table 1: Methods on the IsolatedStorageFile class.

Here's a quick example that shows how to create a new directory:

using (var iso = IsolatedStorageFile.GetUserStoreForApplication())
{
   iso.CreateDirectory("MyStuff");
}

What if you need to create a child subdirectory? All that you have to do is getting hold of a directory name that includes both the parent directory and the new directory. If you can't provide the full name of the directory as a hard-coded string, you can use the Path class to build the name programmatically. Here's how to do it:

string subdir = Path.Combine("MyStuff", "Photos");
iso.CreateDirectory(subdir);

It is worth recalling that in Silverlight you can only use relative names to refer to files and directories as if they were scoped to the root of the filesystem. Well, this is exactly what happens here; except that we're talking about a virtual filesystem.

To enumerate the content of a directory, whether you want to list files or child directories, you use ad hoc methods such as GetFileNames and GetDirectoryNames. Both methods return an array of strings with all the names that match the specified search criteria.

string[] filelist = iso.GetFileNames("*.txt");

Both GetFileNames and GetDirectoryNames have two overloads. If you use the parameterless overload, then the returned array includes all elements found. If you specify a search pattern, keep in mind that you can use both single character (?) and multiple character (*) wildcards.

In Silverlight, you can't make any use of the DirectoryInfo and FileInfo classes that in the full .NET Framework are so helpful when it comes to do any special work on files and directories. The following code, though, compiles successfully:

DirectoryInfo dir = new DirectoryInfo(@"c:\");

In other words, the DirectoryInfo class, as well as the companion FileInfo class, is an integral part of the .NET Framework that ships with Silverlight. The problem with the previous code is just security. No developer code can be executed in Silverlight that invokes a class method that is flagged with the SecurityCritical attribute. As you can see in Figure 1, all methods of the DirectoryInfo class, including the constructors, fall in this category.

Figure 1:Dissecting the source code of the DirectoryInfo class.

The DirectoryInfo class is defined in the Silverlight base class library but it is not available to developer code. It can only be invoked from system code that is, in turn, decorated with the SecuritySafeCritical attribute. No developer code can ever be decorated with the SecuritySafeCritical attribute. Any programmatic access to DirectoryInfo, or other classes or methods flagged with a security critical attribute, results in an exception of type MethodAccessException.

Working with File Streams

You have learned by now that there's at least one way in Silverlight to obtain a stream from a newly created, or an existing, file. The CreateFile and OpenFile methods on the IsolatedStorageFile class just serve this purpose. Silverlight supports one particular type of stream, represented with the IsolatedStorageFileStream class. Therefore, an alternative way of getting a file stream in Silverlight is just creating explicitly a new instance of the file stream class. Here's how you can proceed:

using (IsolatedStorageFile iso = 
       IsolatedStorageFile.GetUserStoreForApplication()) 
{
    IsolatedStorageFileStream stream;
    stream = new IsolatedStorageFileStream(
            TESTFILE, FileMode.OpenOrCreate, iso);
    :
}

The constructor of the IsolatedStorageFileStream class accepts the name of the file to access and a FileMode value that sheds some light on your intentions about the file. The FileMode enumeration contains the values listed in Table 2. Finally, the third parameter is the reference to the root of the virtual filesystem. Once you have got a stream to operate on the content of a file -- for reading or writing -- you have two options to proceed.

Silverlight and the Local Storage

Value

Description

Append

Opens the file and seeks to the end of the file. A write-only mode, appends any written content at the bottom of the file.

Create

Creates a new file. If the file exists already it gets overwritten.

CreateNew

Creates a new file. If the file exists already an exception is thrown.

Open

Opens the specified file. If the file does not exist an exception is thrown.

OpenOrCreate

Opens the specified file. If the file does not exist, it will be created.

Truncate

Opens the specified file and truncates its size to zero.

 

Table 2: The FileMode enumeration.

The first option entails that you use the synchronous or asynchronous API for I/O defined for streams classes. The stream class features pairs of methods like Read/Write and BeginRead/BeginWrite in addition to classic stream methods such as Flush, Close, SetLength, and Seek. Finally, a stream lists a number of properties including CanRead, CanWrite, Length, and Position.

The second option is based on the fact that you leverage a helper reader or writer class to work with the stream content. Like in the full .NET Framework, these classes are named StreamReader and StreamWriter and have nearly the same programming model. The following code snippet shows how to create a file and write some content to it using a stream writer helper class:

using (IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication()) { IsolatedStorageFileStream stream; stream = new IsolatedStorageFileStream( TESTFILE, FileMode.OpenOrCreate, iso); StreamWriter writer = new StreamWriter(stream); writer.Write(DateTime.Now.ToString()); writer.Close(); stream.Close(); }

Largely similar is the code that reads content out of a stream. Here's the part that relates to the StreamReader class:

StreamReader reader = new StreamReader(stream);
content = reader.ReadToEnd();
reader.Close();

The Write method of the StreamWriter class has a number of overloads to let you write content of a bunch of types -- bytes, arrays, characters, Booleans, objects. The StreamReader class, instead, is mostly a text reader class and offers facilities to read text files.

What if you need to deal with binary content? Silverlight has in store for you also a tailor-made version of the pair BinaryReader and BinaryWriter .NET Framework classes.

Dealing with Quotas

Isolated storage in Silverlight addresses a recurring problem that also Flash developers have to deal with — the volatility of the browser. If the user clears the browser's cache, everything the application may have downloaded is gone. To some extent, isolated storage is the Silverlight's counterpart to Web cookies. Well, but cookies are just extremely small containers of data. What is the maximum size, if any, of the isolated storage for a single application?

To avoid that downloaded applications flood the local hard disk with their settings and user-specific data, a threshold has been set that indicates the maximum capacity of the user store for a given application.

By using the Quota property on the IsolatedStorageFile class, you can learn about the current quota of disk space that the current application is assigned. The AvailableFreeSpace property, instead, tells about any left space. By default, each Silverlight application is given 100 KB of disk space on the local user's machine to save its own data. 100 KB may or may not be enough for some applications. Consider that the quota is per user; so it is certainly more than enough for all those applications that use the isolated storage to save preferences and temporary data such as the draft of some input.

There might be situations, though, where the limit of 100 KB per user is too low. In these cases, the Silverlight base class library makes available a method on the IsolatedStorageFile class through which the application can ask the local user to increase the quota to a given value. The method is TryIncreaseQuotaTo.

The method accepts a long value that indicates the larger size you request and returns a Boolean value that indicates, instead, the user response to the request. Any attempt to increase the application's quota, therefore, must be explicitly approved by the user. So any call to the TryIncreaseQuotaTo method results in a dialog box being displayed to the user, as in Figure 2.

Figure 2: Increasing the disk quota for local storage requires explicit user approval.

The requested size for the quota is normalized by the Silverlight runtime so that the user is prompted to approve increases that fall in the following scale: 100 KB, 1 MB, 5 MB, 10 MB, and so on, up to unlimited. Note also that the TryIncreaseQuotaTo method can only be called from within the UI thread, typically as the result of a user interface event. This is done to ensure that no downloaded application may silently modify a key parameter such as the isolated storage quota without first notifying the user.

Summary

Isolated storage was not invented for Silverlight, but perhaps finds in Silverlight its natural environment. Isolated storage lets Silverlight applications save persistent data on the user machine thus avoiding taxing the Web server with extra space consumption and extra work. Granting Silverlight applications access to local storage doesn't mean enabling any downloaded code to explore the content of the user's filesystem.Isolated storage, in fact, provides access to a sort of virtual filesystem rooted in a hidden and user-specific physical directory. No way can a Silverlight application navigate out of that subtree. Any data persisted through ad hoc file and stream classes is totally protected from other applications. The overall size of the persisted data is subject to a quota. The default value of the quota is 100 KB. This value can be programmatically modified, but the operation cannot go unattended and an explicit user approval — clicking Yes on a dialog box — is always required.

Microsoft's Fickle APIs

A Whole New Ball Game: Aspects of Mobile Application Development

Silverlight 4 Makes it Easy to Count Logical Cores

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.