Chapter 4. Package data and roaming

Almost every app requires the ability to maintain some state on behalf of itself and the user. Typically, the Microsoft .NET Framework has offered a technology known as isolated storage to enable this. But now, Windows provides this technology built into the OS itself. When a package is registered (installed) for a user, Windows prepares storage that your package’s app can write to and read from on behalf of the user. This storage is tied to the package. When the package is unregistered (uninstalled), Windows automatically destroys this storage. The storage is shared and accessible to all apps that run as part of the package (in the same app container [as discussed in the appendix]); this includes any background tasks (as discussed in Chapter 9).

Using package data is quite simple. You just need to work with the ApplicationData class, which looks like this:[28]

public sealed class ApplicationData {
   // Call this first to get access to your package's ApplicationData object
   public static ApplicationData Current { get; }
   // Returns a folder to the package's temporary file store
   // NOTE: The files in this folder may be destroyed by the system at any time
   public StorageFolder TemporaryFolder { get; }
   // Members to access data that always resides on the user's PC
   public ApplicationDataContainer LocalSettings { get; }
   public StorageFolder LocalFolder { get; }
   // Members to access data that can roam across all of the user's PCs
   public ApplicationDataContainer RoamingSettings { get; }
   public StorageFolder RoamingFolder { get; }
   public UInt64 RoamingStorageQuota { get; }
   public void SignalDataChanged();
   public event TypedEventHandler<ApplicationData, Object> DataChanged;
   // Members related to versioning the package's data.
   // Typically used when the user upgrades to a new version of the package.
   public UInt32 Version { get; }
   public IAsyncAction SetVersionAsync(
      UInt32 desiredVersion, ApplicationDataSetVersionHandler handler);
   // Members that clear out all (or some) of the package's per-user data
   public IAsyncAction ClearAsync();
   public IAsyncAction ClearAsync(ApplicationDataLocality locality);
}

For your app to access its per-user package data, it must first gain access to its own package data repositories by querying ApplicationData’s Current property:

ApplicationData appData = ApplicationData.Current;

Once you have a reference to your package’s ApplicationData object, you can access all the class’ instance members. The next thing you must decide is the locality of the data. Here is a description of the three data localities:

  • Temporary. Use this locality like a cache. That is, you typically store in here data that is costly or time consuming to acquire. Once you acquire the data, you store it in a file in the TemporaryFolder folder. Note that any files you store in this folder can be destroyed at any time, even while your app is still running. The system has a scheduled task that normally runs weekly to clear this data out. A user can modify the frequency of this using the Windows Task Scheduler. The scheduled task is MicrosoftWindowsApplicationDataCleanupTemporaryState. In addition, the user could always run Windows’ Disk Cleanup utility (CleanMgr.exe).

  • Local. Use this locality for data you need to persist. The system will never destroy whatever data you put here unless the user uninstalls your package.

  • Roaming. Use this locality for any data you want the system to automatically copy across the user’s PCs. Windows Store apps are licensed to a user, and a user is allowed to install a single app on many PCs. This locality causes your package’s data to be the same across all the user’s PCs using an eventual consistency model. The Roaming package data section later in this chapter explores the details of using this locality.

Once you’ve decided the locality of the data, you then decide how to store and access it. There are two ways of storing data:

  • Settings. Settings is a dictionary of key/value pairs. Each key is a string (up to 255 characters long) representing the name of a setting, and the value is some simple WinRT type or a single dimension array of these types that is no more than 8 KB in size. Settings is not just a collection of key/value pairs; it is a hierarchical collection of containers (up to 32 levels deep). That is, you can create a tree of containers to store key/value pair collections, allowing you to organize your package’s data however you like.

  • Storage folder. Storage folders are for items inappropriate for settings, such as items over 8 KB in size and items you want to treat as files (images, videos, music). You can create whatever files you want in these folders; you can also create subdirectories, allowing you to organize your data hierarchically. In the files, you can store whatever data you’d like. Files really contain arrays of bytes, but you can use rich .NET data types if you make them serializable and then use normal .NET mechanisms (like DataContractSerializer) to serialize the objects to byte arrays. See Chapter 5 and Chapter 6 for more information about managing files and their contents.

Of course, the main purpose of package data is that it persists across invocations of your app. That is, if your app gets terminated, your package data continues to live. Also, package data continues to live even if the user upgrades from one version of your package to a newer version. But, when this happens, you might want to change the schema of the package data. To do this, the ApplicationData class provides members allowing you to version the data; this will be discussed in this chapter’s Versioning package data section.

Package data settings

Here is code demonstrating how to write and read a setting from local package data:

// Gain access to our package's data repositories
ApplicationData appData = ApplicationData.Current;
// Store an item in the collection: Key="DataUpdatedAt", Value=DateTimeOffset.Now
appData.LocalSettings.Values["DataUpdatedAt"] = DateTimeOffset.Now;
// Later, we can read it back:
DateTimeOffset dataUpdatedAt = (DateTimeOffset) appData.LocalSettings.Values["DataUpdatedAt"];

You can store only simple WinRT (not .NET) data types in settings. The valid WinRT data types are Boolean, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Char, String, DateTimeOffset, TimeSpan, Guid, Point, Size, and Rect. You can also store single-dimension arrays of these data types, but the resulting value cannot be larger than 8 KB in size. However, there is no limit to the number of the key/value pairs you can add to the collection. The Values property returns an ApplicationDataContainerSettings object, which implements the IObservableMap<String, Object> interface, which offers a MapChanged event that is raised whenever an item in the collection changes.

In addition to the simple data types, there is one other special data type you can use: ApplicationDataCompositeValue. This type allows you to store multiple pieces of data atomically. For example, if you had this:

String videoName = ...;
appData.LocalSettings.Values["LastVideoWatched"] = videoName;
// Imagine an unhandled exception occurs here!
Int32 videoPosition = ...;
appData.LocalSettings.Values["LastVideoPosition"] = videoPosition;

Then it is possible that your app could store the last video the user was watching and then terminate before storing the user’s position within the movie. If the user later restarts the app, it loads the last video the user was watching but positions it at the last place in the previous video the user was watching. This would result in a very poor user experience. You can rewrite the code using the ApplicationDataCompositeValue to fix this:

ApplicationDataCompositeValue compositeValue = new ApplicationDataCompositeValue {
   { "LastVideoWatched", videoName },
   { "LastVideoPosition", videoPosition }
};
appData.LocalSettings.Values["LastVideoInfo"] = compositeValue;
// This shows how to read the information back out:
compositeValue = (ApplicationDataCompositeValue) appData.LocalSettings.Values["LastVideoInfo"];
videoName = (String) compositeValue["LastVideoWatched"];
videoPosition = (Int32) compositeValue["LastVideoPosition"];

Now, the assignment of the ApplicationDataCompositeValue object into settings is an all-or-nothing transaction so that an unhandled exception means that the next time our app starts up it will either have the old video information or the latest video information; it cannot have some old and some new information. A single ApplicationDataCompositeValue object can contain up to 64 KB of data.

When building and debugging an app, it can be useful to know how Windows internally persists your package’s data settings. The settings are stored in a registry hive file called Settings.dat. You can find this file in the following directory: %LocalAppData%PackagesPackageFamilyNameSettings. You can view and modify a package’s data settings by loading this file into RegEdit.exe. To do this, start RegEdit.exe, select HKEY_LOCAL_MACHINE, and then select File, Load Hive. In the Load Hive dialog box, open the Settings.dat file, and give the hive a key name. Then expand the HKEY_LOCAL_ACHINE node, select the key name you just created, and explore. Although it is easy to see all the key values this way, unfortunately all the data is shown as byte arrays, which makes it a bit difficult to read and modify.

As an alternative, you can use Wintellect’s Package Explorer desktop app (discussed in Chapter 2) to see all of a package’s data settings. Package Explorer uses the publicly documented Windows.Management.Core.ApplicationDataManager class’ CreateForPackageFamily method to access a package’s data.

The ability for desktop applications to access a Windows Store package’s data is quite powerful. For example, you could create a tool that exports and imports a package’s data. This can be useful for repeatedly testing against a known set of initial data. Also, companies can produce desktop diagnostic tools that customers can use to capture information about their environment (including package data) to send back to the company to help diagnose customer issues. And, once package data is exported, it can be imported by many PCs, enabling a way to configure all these PCs identically. For example, an enterprise can use a desktop app or script to preset a Windows Store app’s configuration settings, connection strings, and so on.

Package data storage folders

Here is code demonstrating how to write and read a file from a package’s local storage folder:

// Gain access to our package's data repositories
ApplicationData appData = ApplicationData.Current;
// Store some text in a file:
StorageFile file = await appData.LocalFolder.CreateFileAsync("SomeAppData");
await FileIO.WriteTextAsync(file, "Here is some data to persist");
// Later, we can read it back:
file = await appData.LocalFolder.GetFileAsync("SomeAppData");
String persistedData = await FileIO.ReadTextAsync(file);

Files store only arrays of bytes, but there are many ways of converting richer data types into byte arrays. For example, FileIO’s WriteTextAsync and ReadTextAsync methods shown in the previous code convert strings to and from byte arrays by way of a UTF-8 encoding. For full details about manipulating files, see Chapter 5 and Chapter 6.

Windows does not impose limits on a package’s data files. Files can be extremely large (264 bytes), and there is no restriction on the number of package data files you can create. So package data files can fill all of the user’s hard disk. To see how much disk space individual packages are using, go to Settings charm > Change PC Settings > Search And Apps, App Sizes. The indicated size is generally the sum of the package itself and its folders.

For debugging purposes, it is useful to know that all of a package’s storage files can be found in the following three directories:

  • Temporary files  %LocalAppData%PackagesPackageFamilyNameTempState

  • Local files  %LocalAppData%PackagesPackageFamilyNameLocalState

  • Roaming files  %LocalAppData%PackagesPackageFamilyNameRoamingState

Versioning package data

Over time, as you revise your app (creating new versions of its package), you’ll likely want to change the kind or format of your package’s data. Consequently, when a user upgrades your package to a newer version, you might want to execute a routine that updates your package’s data. To help you manage your package’s data, the system associates a version number (a 32-bit unsigned integer) with it. By default, your package’s data is assigned a version number of 0. You query your package’s data version number as follows:

var appDataVersion = ApplicationData.Current.Version;

Note that there does not have to be a relationship between versioning your package and versioning your package’s data. That is, the first three versions of your package can all use version 0 of your package’s data. And then, for version 4 of your package, you might decide to upgrade your package data to version 1. In this example, when the user first runs version 4 of your package’s app, you’ll want to upgrade your package’s data.

When you do decide to upgrade your package data version, you’ll need to write a method that transforms an earlier version of your package’s data to the newest version. The method you write should look like this:

private async void AppDataSetVersion(SetVersionRequest setVersionRequest) {
   // To upgrade files, leave this method 'async', use the deferral & 'await' file I/O methods.
   // If you're only upgrading settings, delete 'async' and the deferral-related code
   var deferral = setVersionRequest.GetDeferral();
   switch (setVersionRequest.CurrentVersion) {
      case 0:
         // TODO: Code to convert from version 0 to latest version goes here
         break;
      case 1:
         // TODO: Code to convert from version 1 to latest version goes here
         break;
   }
   deferral.Complete();
}

You invoke this method by using code like this:

const UInt32 appDataLatestVersion = 2;
if (ApplicationData.Current.Version < appDataLatestVersion)
   ApplicationData.Current.SetVersionAsync(appDataLatestVersion, AppDataSetVersion);
      .AsTask().GetAwaiter().GetResult();

Note that when your AppDataSetVersion handler method returns, the system associates the latest version number with your package data so that future calls to ApplicationData’s Version property return the latest version. Also, note that if your AppDataSetVersion handler method throws an unhandled exception, the system does not associate the latest version number with your package data; however, some of your package data might have successfully been upgraded to the new version. If an unexpected exception occurs while upgrading package data, you might want to consider destroying all of it (by calling ApplicationData’s ClearAsync method) and reconstruct all of your package’s data from scratch.

Now, the question is where should you put this code? The best place to put this code is inside a background task triggered by a servicing complete system trigger. (See Chapter 9.) This way, when the user installs the latest version of your package, the background task will execute and update your package’s data at that time. However, if you do not have or want to create a background task, you can put this code inside your app itself. If you do this, you will probably want to call SetVersionAsync synchronously (as shown in the previous example). This blocks the thread from making further progress, preventing any other parts of your app code from accessing package data until you’re sure the upgrade is complete. And, if you execute this code synchronously, you can call it from your app’s Main method or inside your App class’ constructor, ensuring that your package data upgrades when your app is activated no matter how it gets activated.[29] The only problem with upgrading the version of your package’s data here is that this executes while your app’s splash screen is displayed, and this delays the user’s ability to interact with your app. So, if upgrading your package’s data can take a long time, you might want to consider upgrading the data while showing an extended splash screen. If you do this, you can avoid upgrading the data synchronously. But be careful not to access the data from other threads until the upgrade is complete.

Note

To help test your AppDataSetVersion handler method, you can clear your package’s data and reset its version number by opening your project’s Properties, selecting the Debugging pane, and then selecting the Uninstall And Then Re-Install My Package check box.

Roaming package data

Increasingly, users have multiple PCs that they use on a regular basis. For example, they might have a PC at home, a PC at work, and a tablet that they travel with. It is a pain for users to configure all these PCs similarly for each app they use. For this reason Windows provides the ability for Windows Store apps to roam any package settings and storage files. Now, a user can configure your app once, and the configuration will automatically roam across all the user’s PCs where the user has installed your package. In addition, your app can offer the user a continuous experience where the user starts an operation with your app on one PC and then continues the operation on a different PC. For example, imagine a user is using your app to watch a video on her desktop PC and then she grabs her tablet and opens your app, and the video continues from where it left off. In fact, the main way for users to force the current PC’s settings to sync with their other PCs is by locking their PC.

Many settings in Windows itself roam across the user’s PCs automatically as well, such as desktop background images, Internet favorites, and language settings. To have settings roam, users must log in to their Windows PC using their Microsoft account or link their local or domain account with their Microsoft account. Users associate their Microsoft account with Windows by opening the Settings charm > Change PC Settings > Accounts > Your Account > Connect To A Microsoft Account.

As with everything in Windows, the user always maintains control of her data and experience. To this end, the user controls what data can and cannot roam using the Settings charm > Change PC Settings > SkyDrive > Sync Settings pane as shown in Figure 4-1. Most of these settings control operating system settings, but App Settings controls the roaming of package data. Users can also specify whether they want to use potentially expensive network connections for roaming. (See Chapter 7.) Note that in an enterprise, administrators can adjust or block roaming with Group Policy for domain-joined machines.

Users control the roaming of their data via the Sync Settings pane.

Figure 4-1. Users control the roaming of their data via the Sync Settings pane.

Having your app support roaming settings is free, and it couldn’t be easier to implement. All you have to do is add settings to ApplicationData’s RoamingSettings or add files to ApplicationData’s RoamingFolder. Windows takes care of everything else. For example, Windows will automatically roam the data when an Internet connection is available, Windows will keep the user’s data secure, and Microsoft provides and maintains the storage servers that persist the user’s data.[30] Note that Microsoft’s servers limit how much storage a package can roam. An app can determine this amount by querying ApplicationData’s RoamingStorageQuota property. Currently, this property returns a value of 100 KB. If your package goes over its quota, Windows stops roaming any of your package’s data.[31] It is up to you to capacity plan how you use your package’s roaming storage. This means that you should roam small pieces of data. For example, instead of roaming content, roam links to the content instead. If you need more than the allowed quota, you will have to build your own roaming infrastructure (servers, authentication and communication mechanisms, and so forth).[32] The purpose of the Windows-provided roaming is to allow any developer, college student, or enthusiast to roam data without having to deal with all these infrastructure details.

Note

Certain files types do not roam. For example, if you put a file with a .zip or .cab extension in the roaming folder, it will not roam. This is particularly troublesome because many developers want to compress the data they roam to get more data in the 100-KB quota. Microsoft does not document the list of file types that do not roam. If you’re not seeing a file you put in the roaming folder roam, try changing the file’s extension.

Important

Do not store passwords in package data. Instead, create a Windows.Security.Credentials.PasswordCredential object with the desired password. Then add the PasswordCredential object to a Windows.Security.Credentials.PasswordVault object. This causes the PasswordCredential object to roam securely across the user’s PCs. Note: When a PasswordCredential object is inserted into a PasswordVault on a domain-joined PC, the PasswordCredential will not roam, to prevent domain credentials from going out over the Internet. Use the Windows’ Credential Manager applet to view the persisted credentials for all apps.

Windows typically syncs a package’s roaming settings and files within a few minutes, but there is no guarantee of this because there could be network-connectivity issues. Because of this, the roaming feature is not designed to enable scenarios where two or more PCs are being used side by side, and the feature is also not designed to be used as a cross-PC communication technology. Also, if the user changes her roaming data on two or more PCs and then Internet connectivity is restored, data that has been written last will be synced across the user’s PCs; older data will be overwritten. As discussed in this chapter’s Versioning package data section, package data is versioned independently of the package itself. Windows will not roam a newer version of package data to a PC whose package data is an older version, because this would most likely cause the older version of the app to malfunction the next time it was activated.

Windows does not sync data when the user logs out or when the PC hibernates or sleeps because this would hurt performance. However, if you are running on a PC that supports connected standby, sync’ing does eventually occur even if the PC is “off.” If a package is installed on just one of a user’s PCs, Windows still syncs the data approximately once per day. This means a user can destroy (or lose) that PC, purchase a new PC, install the package, and the sync’d data will come onto the new PC. If a package is uninstalled from all of a user’s PCs, the package’s roaming data remains in the cloud for about one month before it gets destroyed. During that month, if the user installs the package onto a PC, the persisted data will come onto the PC.

If the user wants to purge all the data (perhaps for privacy reasons) after uninstalling a package, the user can go to https://SkyDrive.Live.com/Win8PersonalSettingsPrivacy/ and click the Remove button. This actually removes all of the user’s roaming data from the cloud, but for packages the user still has installed, Windows will simply copy their roaming data back up to the cloud. Deleting storage from the cloud can be a useful technique when developing an app because it allows you to clear out any bad roaming data created during development. You can also clear out roaming data programmatically by calling ApplicationData’s ClearAsync method, passing in the ApplicationDataLocality.Roaming flag.

In some scenarios, you might want a setting to roam very quickly from one PC to all the others. The video example comes to mind where the user might switch from her desktop PC to her tablet and want to pick up watching the video immediately. In this case, having the video info sync in a few minutes is too slow. To address this scenario, Windows allows a package to consider one setting to be high priority and Windows will do what it can to sync this setting quickly (usually within one minute). You roam one setting very quickly if you give it a key name of "HighPriority" (with this exact casing), and you must store this key in the root ApplicationDataContainer:

// NOTE: Use an ApplicationDataCompositeValue object to roam multiple values quickly
ApplicationDataCompositeValue compositeValue = new ApplicationDataCompositeValue {
   { "LastVideoWatched", videoName },
   { "LastVideoPosition", videoPosition }
};
appData.LocalSettings.Values["HighPriority"] = compositeValue;

You must avoid continuously updating the "HighPriority" setting; instead, update it at specific times, such as when the user pauses or stops a video, when your app gets suspended, or perhaps once every minute. Similarly, you’ll want to read the value when your app is launched, when it gets resumed, or when ApplicationData’s DataChanged event is raised. (See the Package data change notifications section later in this chapter.) If the value of the "HighPriority" setting is an ApplicationDataCompositeValue (as shown in the previous example), it must contain no more than 8 KB of data to have it roam quickly. If it contains more than 8 KB of data, it will roam as quickly as any other normal setting.

To help developers test their package’s roaming data, Microsoft makes a Roaming Monitor Tool that integrates with Visual Studio. You can download it via the Visual Studio Gallery from http://VisualStudioGallery.MSDN.Microsoft.com/3ccf8c24-5e72-4ba0-b3e9-d822ca345fd0. With this tool, you can monitor, view, and manipulate your package’s roaming settings. In addition, you can force the data to roam on demand. Similarly, you can force the system to sync roaming package data to the cloud by executing the following:

SchTasks.exe /run /i /tn MicrosoftWindowsSettingSyncBackgroundUploadTask

After the PC uploads its package data to SkyDrive, a Windows push notification (discussed in Chapter 8) is sent to the user’s other PCs causing them to download the latest package data from SkyDrive.

Finally, to help you with troubleshooting, the system logs roaming-related events in the following system event logs:

  • Applications And Services Logs > Microsoft > Windows > SettingSync

  • Applications And Services Logs> Microsoft > Windows > PackageStateRoaming

Package data change notifications

When Windows copies a package’s roaming data from the cloud onto a user’s PC, Windows raises ApplicationData’s DataChanged event. If your app registers with this event, it can refresh its new roaming settings and modify its behavior on the fly while the user is interacting with your app. Note that Windows raises this event on a thread pool thread, so you’ll have to use a CoreDispatcher if you want to update your app’s user interface.

Windows raises the DataChanged event automatically whenever the PC downloads new roaming package data. But your app can raise this event itself by calling ApplicationData’s SignalDataChanged method. Calling this method is useful if a setting changes in one part of your app and you need to let another part of your app know that settings have changed so that it can query its new value. This is also useful if you need your app to signal a setting change to one of its background tasks (as discussed in Chapter 9) or vice versa.



[28] It’s a shame this class isn’t called PackageData to more accurately reflect that the data is associated with the package, not with an individual app.

[29] C# doesn’t allow you to mark your Main method or a constructor with the async keyword; therefore, you cannot use the await operator in these kinds of methods. But, if you invoke SetVersionAsync synchronously, this is not a ­problem.

[30] Your package’s data is actually in the user’s SkyDrive account, but the storage is not part of the user’s quota and the user has no direct access to the package data maintained by SkyDrive.

[31] Remember, Windows does not limit the amount of data that can be stored in roaming settings or roaming folders, but there is a limit to how much of that can be sync’d with the cloud.

[32] One option is to have your app use SkyDrive APIs to explicitly sync your package’s data with the user’s SkyDrive account. This will require the user’s permission.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.182.62