Design and Implement Data Caching
Windows Store applications have various options to store data, and each has its advantages and disadvantages. The right place to store information or cache depends on the application, the scenario in which the user works, and whether the application relies on HTML5/ JavaScript or on XAML with C#, Microsoft Visual Basic, or C++.
Understanding application and user data
A Windows Store app works with different kinds of data; mainly application data and user data. Application data (also referred to as app data) represents information that the application creates, updates, and deletes; or, in general terms, uses. This kind of data is bound to the application and has no meaning outside of it: The data cannot live without the application because it has no sense outside of the app. Types of application data include the following:
- User preferences:
Any setting the user can personalize in an application, such as choices for browsing, searching, and receiving notifications. For example, the preferred city for a weather app is a user preference, which has no meaning outside of that application. - Runtime state:
Data entered by the user and preserved in a page preserved in the navigation state. Runtime state data has meaning only inside the application. - Reference content:
An item related to the application. For example, the list of cities for a weather application or the list of terms for a translation app is bound to the application.
User data is independent from the application and is usually stored outside the application in a database, nonrelational storage, or file. This kind of data can be used by other applications. Types of user data include these:
- Entities:
The main classes a developer works on. Entities can include invoices, orders, and customer data, such as for an accounting app. - Media files:
Audio files, videos, and photos. - Documents:
Files that can be stored via a web service, Microsoft SkyDrive, or Microsoft SharePoint service.
Application data must be stored in a specific per-app, per-user store. This data cannot be shared across users and needs to be placed in an isolated storage that prevents access from other apps and users. If the user installs an application update, this kind of data must be preserved, but the data must be cleaned if the user removes the application from the system. It is important to store this kind of data in a type of storage that provides these features. Fortunately, Microsoft Windows 8 exposes a storage mechanism that perfectly suits these needs.
You cannot use the same storage mechanism for user data, such as application entities or documents, however. You can use user libraries, SkyDrive, or a cloud service to store user data.
Caching application data
The Windows Runtime (WinRT) offers many solutions to store application data in local and remote storage. This section analyzes the most important ones:
- Application data application programming interfaces (APIs)
- IndexedDB
- Extensible Storage Engine (ESE)
- HTML5 Web Storage
- Windows Library for JavaScript (WinJS)-specific classes, such as sessionState, local, roaming
Application data APIs
The WinRT application data APIs provide access to local and roaming data stores. Simply choose the class and the name of the object; these classes can save and retrieve content from both local and roaming data.
Every Windows 8 application, regardless of the language in which it is written, can save settings and unstructured data files to local and roaming storage. The storage also provides a place to store temporary data. This area is subject to clean-up by the system automatically.
LOCAL STORAGE
You can save and retrieve application settings with a single line of code and compose them using either a simple or complex data type. The settings provide a mechanism to store composite types, which are sets of application settings to be managed as a single unit in an atomic operation. The following code references the LocalSettings and LocalFolder properties of the ApplicationData class in a local variable to be used throughout the code:
Sample of C# code Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
Settings can be saved and retrieved in a synchronous way. File and folder access uses the async/await pattern. The following code saves and then retrieves a setting called PageSize:
localSettings.Values["PageSize"] = "20"; (code omitted) Object value = localSettings.Values["PageSize"];
Settings are limited to 8 kilobytes (KB) per single setting, whereas composite settings are limited to 64 KB. Listing-1 demonstrates the use of a composite setting. The PageSize and Title key-value pair is stored in the PrintSettings composite setting.
LISTING-1 Saving and retrieving a composite setting
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; Windows.Storage.ApplicationDataCompositeValue composite = new Windows.Storage.ApplicationDataCompositeValue(); composite["PageSize"] = 20; composite["Title"] = "DevLeap Members"; localSettings.Values["PrintSettings"] = composite; Windows.Storage.ApplicationDataCompositeValue composite = (Windows.Storage.ApplicationDataCompositeValue)localSettings .Values["PrintSettings "]; if (composite == null) { // No data stored in the composite setting yet } else { // Access data in composite["PageSize"] and composite["Title"] }
If you have a complex settings structure, you can create a container to better understand and manage settings categories. Use the ApplicationDataContainer.CreateContainer method to create a settings container. The following code excerpt creates a settings container named PrintSettingsContainer and adds a setting value named PageSize:
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; Windows.Storage.ApplicationDataContainer container = localSettings.CreateContainer("PrintSettingsContainer", Windows.Storage.ApplicationDataCreateDisposition.Always); if (localSettings.Containers.ContainsKey("PrintSettingsContainer")) { localSettings.Containers["PrintSettingsContainer "].Values["PageSize"] = "20"; }
The second line of code creates a container named PrintSettingsContainer with the s method. The second parameter represents the ApplicationDataCreateDisposition. The value of Always means the container should be created if it does not exist. The ApplicationData- CreateDisposition enum exposes a second value, Existing, that activates the container only if the resource it represents already exists.
To reference a container and retrieve its settings values, you can use the Containers enum to test for container existence and then reference the object to ask for its settings and relative values. The following code uses the ContainsKey method to test for container presence. If the method returns true, it retrieves the value for the PageSize setting.
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; bool hasContainer = localSettings.Containers.ContainsKey("PrintSettingsContainer"); bool hasSetting = false; if (hasContainer) { hasSetting = localSettings .Containers["PrintSettingsContainer"].Values.ContainsKey("PageSize"); }
To delete a setting, you simply call the Remove method, as follows:
localSettings.Values.Remove("exampleSetting");
To delete a container, you can use the DeleteContainer method of the ApplicationData- Container class:
localSettings.DeleteContainer("exampleContainer");
Remember that this area of storage is suitable for settings, not user data. Do not store more than 1 megabyte (MB) of data.
If you have a very complex setting structure or you need to store information locally, you can choose to store these values in a local file instead of a local settings container. As you learn in the next section, local storage gives you a simple way to manage files and folders.
For example, if the application wants to track the time the user performs some operations, the settings container becomes very difficult to manage. A plain text file can be a simple but effective solution to store this log information. The code in Listing-2 creates a file called log.txt to store the current date and time.
LISTING-2 Logging to files in local user storage
async void CreateLog() { Windows.Storage. StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder; Windows.Globalization.DateTimeFormatting.DateTimeFormatter formatter = new Windows.Globalization.DatetimeFormatting.DateTimeFormatter("longtime"); StorageFile logFile = await localFolder.CreateFileAsync("log.txt", CreationCollisionOption.ReplaceExisting); await FileIO.WriteTextAsync(logFile, formatter.Format(DateTime.Now)); }
The code creates a file in the local folder storage called log.txt using the CreateFileAsync method. Then it references the file in the WriteTextAsync method of the FileIO class (of the Windows.Storage namespace). The CreationCollisionOption can be ReplaceExisting to create a new file or FailIfExist to return an error if the file already exists; OpenIfExists to create a new file if it does not exist or open the existing one; and GenerateUniqueName to create a new file with an autogenerated number if a file with the same name already exists.
To read a value from a file, use the code in Listing-3.
LISTING-3 Reading values from files
async void ReadLog() { try { Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder; StorageFile logFile = await localFolder.GetFileAsync("log.txt"); String logDateTime = await FileIO.ReadTextAsync(logFile); // Perform some operation } catch (Exception) { // Problems reading the file } }
ROAMING STORAGE
Settings and files in local storage can be roamed to "follow" the user on different devices and places. For example, a user can set application printing preferences on his Windows 8 desktop and retrieve the same value on his tablet WinRT device. You do not need to manage data transfers because the Windows Runtime gives you the RoamingSettings.
Roaming settings are synchronized by the system that manages the overall process in respect to battery life and bandwidth consumption.
First, you can retrieve the roaming settings or folder reference in a similar way as the local one:
Windows.Storage.ApplicationDataContainer roamingSettings = Windows.Storage.ApplicationData.Current.RoamingSettings; Windows.Storage.StorageFolder roamingFolder = Windows.Storage.ApplicationData.Current.RoamingFolder;
You can then access the settings in a synchronous way. The following code saves and then retrieves a setting called PageSize:
roamingSettings.Values["PageSize"] = "20"; (code omitted) Object value = roamingSettings.Values["PageSize"];
If you need to store complex values, you can use the same strategy you saw for the local settings in Listing-1. Create an ApplicationDataCompositeValue instance, assign the values to it, and then use the RoamingSettings reference to store the instance. See Listing-4.
LISTING-4 Composite setting stored in the roaming user profile
Windows.Storage.ApplicationDataContainer roamingSettings = Windows.Storage.ApplicationData.Current.RoamingSettings; Windows.Storage.ApplicationDataCompositeValue composite = new Windows.Storage.ApplicationDataCompositeValue(); composite["PageSize"] = 20; composite["Title"] = "DevLeap Members"; roamingSettings.Values["PrintSettings"] = composite; Windows.Storage.ApplicationDataCompositeValue composite = (Windows.Storage.ApplicationDataCompositeValue)roamingSettings .Values["PrintSettings"]; if (composite == null) { // No data stored in the composite setting yet } else { // Access data in composite["PageSize"] and composite["Title"] }
You can group roaming settings in containers to manage complex structures easily. The following code excerpt creates a settings container named PrintSettingsContainer and adds a setting value named PageSize:
Windows.Storage.ApplicationDataContainer roamingSettings = Windows.Storage.ApplicationData.Current.RoamingSettings; Windows.Storage.ApplicationDataContainer container = roamingSettings.CreateContainer( "PrintSettingsContainer", Windows.Storage.ApplicationDataCreateDisposition.Always); if (roamingSettings.Containers.ContainsKey("PrintSettingsContainer")) { roamingSettings.Containers["PrintSettingsContainer "].Values["PageSize"] = "20"; }
The first line of code creates a container PrintSettingsContainer using the CreateContainer method. The second parameter represents the ApplicationDataCreateDisposition. The value of Always means the container should be created if it does not exist yet. The enum also contains a second value, Existing, that activates the container only if the resource it represents already exists.
To reference a container and retrieve its settings values, you can use the Containers enum to test for container existence and then reference the object to ask for its settings and relative values. The following code uses the ContainsKey method to test for container presence. If the method returns true, it retrieves the value for the PageSize setting.
Windows.Storage.ApplicationDataContainer roamingSettings = Windows.Storage.ApplicationData.Current.RoamingSettings; bool hasContainer = roamingSettings.Containers.ContainsKey("PrintSettingsContainer"); bool hasSetting = false; if (hasContainer) { hasSetting = roamingSettings .Containers["PrintSettingsContainer"].Values.ContainsKey("PageSize"); }
To delete a setting you simply call the Remove method:
roamingSettings.Values.Remove("exampleSetting");
To delete a container, you can use the DeleteContainer method of the ApplicationData- Container class:
roamingSettings.DeleteContainer("exampleContainer");
As for local storage, you can use a class to store a file in the roaming user profile. The code in Listing-5 creates a file called log.txt to store the current time in the roaming profile.
LISTING-5 Logging to a file in roaming user storage
async void CreateLog() { Windows.Storage.StorageFolder roamingFolder = Windows.Storage.ApplicationData.Current.RoamingFolder; Windows.Globalization.DateTimeFormatting.DateTimeFormatter formatter = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime"); StorageFile logFile = await roamingFolder.CreateFileAsync("log.txt", CreateCollisionOption.ReplaceExisting); await FileIO.WriteTextAsync(logFile, formatter.Format(DateTime.Now)); }
This code is similar to the code for accessing local storage. It creates a file in roaming storage called log.txt using the CreateFileAsync method. Then it references the file in the Write- TextAsync method of the FileIO class (of the Windows.Storage namespace). The CreateCollisionOption can be ReplaceExisting to create a new file or FailIfExist to return an error if the file already exists; OpenIfExists to create a new file if it does not exist or open the existing one; and GenerateUniqueName to create a new file with an autogenerated number if a file with the same name already exists.
To read a value from a file, use the code shown in Listing-6.
LISTING-6 Reading values from a file in the roaming profile
async void ReadLog() { try { Windows.Storage.StorageFolder roamingFolder = Windows.Storage.ApplicationData.Current.RoamingFolder; StorageFile logFile = await roamingFolder.GetFileAsync("log.txt"); String logDateTime = await FileIO.ReadTextAsync(logFile); // Perform some operation } catch (Exception) { // Problems reading the file } }
Files in the local settings space have no limits, whereas roaming settings are limited to the quota specified by the RoamingStorageQuota property of the ApplicationData class. If you exceed that limit, synchronization does not occur.
You can be notified when data changes in the roaming profile of the user. Let's say an application on the desktop changes some date in the roaming profile. The application on a different user device can be notified and react to this change, refreshing the user interface. The following code uses the DataChanged event to handle this situation:
Windows.Storage.ApplicationData.Current.DataChanged += new TypedEventHandler<ApplicationData, object>(DataChanged); void DataChanged(Windows.Storage.ApplicationData appData, object o) { // Refresh data or inform the user. }
TEMPORARY STORAGE
If your application needs temporary data, you can use the temporary space reserved by the Windows Runtime to store this information. The code is similar to code for local and roaming storage, but uses different classes to obtain the reference to the store.
Temporary storage is not limited in any way apart from the physical disk space.
Let's say that the log that tracks the time of every operation presented in the previous samples (refer to Listings-2 and-5) is not permanent; the application simply stores log information that is useful just for a user session on the application. The code in Listing-7 uses the TemporaryFolder property of the ApplicationData.Current class to retrieve the temporary storage folder and then create a file in it.
LISTING-7 Writing log files in the temporary folder
async void CreateLog() { Windows.Storage.Storage tmpFolder = Windows.Storage.ApplicationData.Current.TemporaryFolder; Windows.Globalization.DateTimeFormatting.DateTimeFormatter formatter = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime"); StorageFile logFile = await tmpFolder .CreateFileAsync("log.txt", CreationCollisionOption.ReplaceExisting); await FileIO.WriteTextAsync(logFile, formatter.Format(DateTime.Now)); }
The code is similar to the code for accessing local storage. It creates a file in the temporary storage called log.txt using the CreateFileAsync method. Then it references the file in the WriteTextAsync method of the FileIO class (of the Windows.Storage namespace). The Creation- CollisionOption can be ReplaceExisting to create a new file or FailIfExist to return an error if the file already exists; OpenIfExists to create a new file if it does not exist or open the existing one; and GenerateUniqueName to create a new file with an autogenerated number if a file with the same name already exists.
To read a value from a file, use the code shown in Listing-8.
LISTING-8 Reading a value from a temporary file
async void ReadLog() { try { Windows.Storage.Storage tmpFolder = Windows.Storage.ApplicationData.Current.TemporaryFolder; StorageFile logFile = await tmpFolder.GetFileAsync("log.txt"); String logDateTime = await FileIO.ReadTextAsync(logFile); // Perform some operation } catch (Exception) { // Problems reading the file } }
Using temporary storage enables your app to be fast and fluid when the use of a network is limited. For example, a weather application can immediately display weather information that was cached to disk from a previous session. After the latest information is available, the app can update its content gracefully, ensuring that the user has content to view immediately upon launch while waiting for new content updates.
IndexedDB
IndexedDB technology enables a Windows Store app written in HTML and JavaScript to store Indexed Sequential Access Method (ISAM) files that can be read using sequential or indexed cursors. This storage has some limits:
- 250 MB per application
- 375 MB for all applications if the hard drive capacity is less than 30 gigabytes (GB)
- 4 percent of the total drive capacity for disks greater than 30 GB with a limit of 20 GB maximum
- Page's URL must be listed in the ApplicationContentUriRules of the application manifest, unless you set the ms-enable-external-database-usage meta tag in the application home (start) page
The following JavaScript code demonstrates how to access IndexedDB:
try { var db = null; var sName = "CustomerDB"; var nVersion = 1.2; if (window.indexedDB) { var req = window.indexedDB.open( sName, nVersion ); req.onsuccess = function(evt) { db = evt.target.result; doSomething( db ); } req.onerror = handleError( evt ); req.onblocked = handleBlock( evt ); req.onupgradeneeded = handleUpgrade( evt ); } } catch( ex ) { handleException( ex ); }
Extensible Storage Engine (ESE)
This engine provides ISAM storage technology to save and access files. Access can be done using indexed or sequential navigation. ESE can be used in transactional code and provides a robust data consistency with transparent mechanisms to the developer. It also provides for fast data retrieval based on caching technology; it is considered a scalable technology for large files (over 40 GB).
The drawback of this technology is the fact that the API is provided only in C/C++. The call to this API must be wrapped in a WinRT component to be accessed by a Windows Store application written in a language different from C++.
HTML5 Web Storage
Any HTML and JavaScript application; or a Windows Store app using C++, C#, or Visual Basic using the WebView control can use HTML5 Web Storage.
These APIs are suitable for storing key-value pairs of strings using the web standard. The APIs are synchronous and enable the application to store 10 MB per single user. The storage is isolated on a per-user and per-app basis, so there is no conflict between apps, and it offers both local and session storage.
You can use the provided localStorage class to store data locally if you need lightweight storage in a web context and the Windows Runtime is not available. Be aware that if you use the sessionStorage class instead of the localStorage class, data exists only in memory and does not survive application termination. The sessionStorage can be used for transient data.
WinJS.Application.sessionState
As the name implies, this store is available for Windows Store apps using JavaScript in the local context, and is suitable to store transitory application state during suspension to resume them when the application resumes.
This store can contain JavaScript objects with application-defined properties and is automatically persisted during suspension to the local ApplicationData class. There is no size limit. If you want to use a web standard to store settings that can be shared to non-Windows Store apps, using WinJS.Application.sessionState is one of the preferred methods.
WinJS.Application.local
The second storage option dedicated to Windows Store apps using JavaScript is a wrapper for the ApplicationData class that makes the use of the local context or web context transparent for the developer. In the local context, the wrapper uses local storage to persist data; in the web context, it falls back to in-memory.
WinJS.Application.roaming
The third storage option dedicated to Windows Store apps using JavaScript is a wrapper for the ApplicationData class that makes the use of roaming profiles or the web context transparent for the developer. In the local context, the wrapper uses the roaming storage to persist data; in the web context it falls back to in-memory.
Understanding Microsoft rules for using roaming profiles with Windows Store apps
Microsoft recommends some general guidelines for using roaming profiles with Windows Store apps. Use roaming settings for settings the user can reuse on a different device. Many users commonly work on two or more different devices, such as a desktop at work, a tablet at home, and/or a notebook while traveling. Using roaming settings enables the user to reuse all preferences on every device. When the user installs the application on a secondary device, all user preferences are already available in the roaming profile. Any changes to user settings are propagated to the roaming profile and applied to the settings on all devices.
These considerations apply to session and state information as well. Windows 8 roams all this data, enabling the user to continue to use a session that was closed or abandoned on one device when he uses a secondary device. For example, Vanni is playing his favorite game at home on his desktop. If he leaves the house, he can take his tablet and continue playing the game.
Using user data in a roaming profile is as easy as using local data, with just a few differences in class names. Use roaming data for anything that can be reused on a different device without modifications, such as color preferences, gaming scores, and state data. Do not use roaming profiles for settings that can differ from device to device. For example, be careful about display preferences. It is not practical to store the number of items to be displayed in a list because a desktop screen can easily represent 50 elements in an ordered list when a small tablet screen can become unreadable with just 20 elements.
To give you some examples, use the roaming profile for the following:
- Favorite football team (sports news app)
- First category to display in a page for a news magazine
- Color customization
- View preferences
- Ordering preferences
Some examples of state data include:
- The last page read for an e-book app
- The score for a game and relative level
- Text inserted in a text box
Do not use roaming data for information bound to a specific device, such as the following:
- The number of items to be displayed in a list
- History of visited pages
- Global printing preferences
Do not use roaming data for large amounts of data. Remember that the roaming profile has quota limits. The roaming profile does not work as a synchronization mechanism, either. Windows 8 transfers data from a device to the roaming profile using many different parameters to determine the best time to start a transfer. This is also true for downloading the profile or changing the profile from other devices. This method is not reliable for instantly passing information from one device to another.
Finally, to use roaming profiles, verify that all prerequisites are met:
- The user must have a Microsoft account.
- The user has used the Microsoft account to log on to the device.
- The network administrator has not switched off the roaming feature.
- The user has made the device "trusted."
Caching user data
The following points summarize the complete reference of where to store the different kinds of information and the pros and cons of each storage mechanism.
User data can be stored here:
- Libraries
- SkyDrive
- HTML5 API
- External service, third-party database, and cloud storage
Libraries
Every Windows Store app, whether written in C++, JavaScript, C#, or Visual Basic, can use libraries for storage. Even DirectX apps written in C++ can use libraries.
The StorageFile class and file pickers are the primary mechanisms for accessing user libraries. There are no size limitations and all the APIs are asynchronous.
File and folder pickers are useful when you want the user to participate in the selection of library items to manage or create.
Programmatic access requires a capability in the application manifest for each library the application needs to access. In the Downloads library, the code can write any file but can read only the file the app wrote. In practice, you cannot access files downloaded from other applications.
This storage is intended to survive application updates and installation because it represents user libraries, not application libraries. In practice, libraries exist by means of Windows 8. A library can also be used independently from the user that runs the application.
SkyDrive
All Windows Store apps, including DirectX apps written in C++, can use SkyDrive for storage. SkyDrive supports storage for user data in the cloud and can be shared across devices, applications, and even platforms.
SkyDrive is not a relational database; it is a shared storage service for items such as documents, photos, music, and videos. You can use SkyDrive storage to share content with other users and apply a size quota based on the level of the user account.
MORE INFO SKYDRIVE-SUPPORTED FILE FORMATS
For a list of supported file formats, see the SkyDrive documentation available at http://msdn.microsoft.com/en-us/library/live/hh826521.aspx.
You can use Live Connect Representational State Transfer (REST) APIs with JavaScript Object Notation (JSON) payloads to work with SkyDrive.
HTML5 File API
Like SkyDrive, all Windows Store apps can use HTML5 File API storage, including DirectX apps. HTML5 File APIs are web standard APIs for uploading and downloading files from a running application. An application can be suspended if it's not in the foreground. For transfers that continue while the application is not in the foreground, use the BackgroundTransfer class.
HTML5 Application Cache API
A Windows Store app using JavaScript supports AppCache (formerly Application Cache API), as defined in the HTML5 specification, which enables pages to use offline content. AppCache enables web pages to save resources locally, including images, script libraries, style sheets, and other HTML-defined resources. In addition, AppCache enables URLs to be served from cached content using standard uniform resource identifier (URI) notation. By using AppCache to save resources locally, you improve web page performance by reducing the number of requests made to the Internet.
External service, server application, third-party database, and cloud storage
Windows 8 does not provide a native local database for Windows Store apps or a way to interact with data access libraries for a database. You cannot access SQL Azure directly, nor can you access SQL Server or SQL Express locally. There are no relational database APIs in the Windows Runtime.
To overcome this limitation, you can store and access data locally using a third-party solution or you can rely on web services to interact with a service-oriented architecture (SOA)/ REST back end. For example, a Windows Communication Foundation (WCF) service can be hosted in the Microsoft Windows Azure platform to bridge an SQL Azure database from Windows Store apps.
If the application uses some form of web service or website, the app can use other ways to store data. For example, if the application makes HTTP calls to a website or web application, it can use the standard web cookie mechanism to share data with each request to the server. Applications written in JavaScript for both web and local context can use cookies directly; applications written in C++, C#, and Microsoft Visual Basic can leverage cookies via the WebView control. Any other apps can use cookies via the IXMLHTTPRequest2 interface.
Cookies are a web standard and well-known technology but have a 4 KB limit per cookie. Cookies in the local context to the web via XMLHttpRequest (XHR) are subject to web crossdomain rules.
If a JavaScript-based application reads data from the web, you can leverage the HTML5 AppCache to make data available offline. This is another web standard technique that caches server response content locally and enables prefetching of web-based markup, scripts, CSS, and resources in general that do not contain code.
Any Windows Store app can consume web services using SOAP or REST and become the new user interface for every kind of existing or new solutions.