In un'applicazione Windows Store, abbiamo principalmente a che fare con due tipi di dati: dati applicativi (application data) e dati dell'utente (user data). I primi includono le informazioni di cui l'app ha bisogno per poter funzionare e rispondono a logiche interne all'applicazione medesima. In altri termini, si tratta di dati funzionali all'applicazione, il cui significato dipende dal contesto applicativo: fuori dall'app, essi non avrebbero infatti alcun senso. Questo tipo di dati includono le preferenze dell'utente, ossia qualunque impostazione che l'utente può configurare per personalizzare l'applicazione (come ad esempio le località preferite in un'app meteo); i dati inseriti dall'utente in una pagina e memorizzati come parte dello stato dell'app a runtime; ecc.
Gli user data, al contrario, sono indipendenti dall'applicazione e sono generalmente conservati in uno store separato, come database, storage non relazionali o servizi accessibili dall'esterno. Questo tipo di dati può essere utilizzato anche da altre applicazioni, e includono entità (come ordini, dati di fatturazione, ecc.), file multimediali (video, foto, audio), documenti (magari da condividere tramite SkyDrive o SharePoint), ecc.
I dati applicativi devono essere salvati in uno store specifico per ciascuna app e ciascun utente, non potendo essere condivisi tra app o utenti diversi. Inoltre, se l'utente aggiorna l'applicazione, i dati devono essere preservati, mentre se l'utente disinstalla l'app, essi devono essere rimossi. Per fortuna, WinRT espone meccanismi di storage che soddisfano tutti questi requisiti. Tieni tuttavia presente che non è possibile sfruttare questo stesso meccanismo per user data, come entità applicative o documenti: per questi, puoi usare le librerie utente, OneDrive (precedentemente noto come SkyDrive), o un servizio cloud.
WinRT offre diverse strade per salvare dati applicativi, sia nello storage locale che in remoto. Alcune di queste opzioni, come le Application Data API o le librerie dell'utente, sono accessibili tramite uno qualunque dei linguaggi supportati da WinRT, mentre altri sono specifici per applicazioni Windows Store in HTML5/JavaScript (come IndexedDB, HTML5 Web Storage, ecc.) o in C++ (Estensible Storage Engine).
Application data API
Le Application Data API di WinRT consentono di accedere allo storage locale o in roaming per salvarvi i dati applicativi. Questo vale per qualunque app Windows Store, a prescindere dal linguaggio utilizzato.
Vediamo entrambe queste opzioni nel dettaglio.
Lo storage locale
Salvare dati applicativi nello storage locale è un'operazione tutt'altro che complessa. È possibile salvare questi dati come semplici coppie chiave/valore, oppure comporle per formare tipi complessi, da gestire tramite operazioni atomiche. Il prossimo snippet mostra un esempio di tipo composto:
var composite = new Windows.Storage.ApplicationDataCompositeValue();
composite["FirstItem"] = "The answer is";
composite["SecondItem"] = 42
;
Ricordiamo che ciascuna coppia chiave/valore semplice può occupare fino a 8 KB, mentre un tipo complesso fino a 64 KB.
Il modo più semplice di salvare (o recuperare) i dati applicativi è rappresentato dalla proprietà LocalSettings
esposto dalla classe ApplicationData
. Questo oggetto, di tipo ApplicationDataContainer
, contiene i dati applicativi (semplici o composti) sotto forma di coppie chiave/valore. Il seguente codice mostra come salvare dati composti nello storage locale e come recuperarli:
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
Il seguente codice mostra come salvare e rileggere dati applicativi complessi:
function saveAppData_click(args) {
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var composite = new Windows.Storage.ApplicationDataCompositeValue();
composite["Title"] = "Html.it Demo";
composite["Content"] = "Contenuto d'esempio";
localSettings.values["MySettings"] = composite;
}
function retrieveAppData_click(args) {
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var composite = localSettings.values["MySettings"];
if (composite == null) {
// Nessun dato disponibile
}
else {
var title = composite["Title"];
var content = composite["Content"];
}
}
Nel caso in cui la struttura dati sia particolarmente complessa, possiamo creare un container per gestire categorie di dati tramite il metodo ApplicationDataContainer.CreateContainer
. Il codice che segue crea un container denominato "MySettingsContainer", passando come parametri il nome del container e un valore dell'enum ApplicationDataCreateDisposition
. Questo enum permette di specificare la politica da seguire durante la creazione: in particolare, Always
significa che il container deve essere creato se non esiste già, mentre Existing permette di accedere a un container già creato in precedenza:
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var container = localSettings.createContainer("MySettingsContainer", Windows.Storage.ApplicationDataCreateDisposition.always);
if (localSettings.containers.hasKey("MySettingsContainer")) {
localSettings.containers.lookup("MySettingsContainer").values["Title"] = "Html.it Demo";
}
Una volta creato il container (e dopo esserci accertati che il container sia stato effettivamente creato), il codice salva una nuova impostazione.
Il seguente codice, dopo aver verificato l'esistenza tanto del container quanto dell'impostazione desiderata, recupera il valore salvato in precedenza:
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var hasContainer = localSettings.containers.hasKey("MySettingsContainer");
if (hasContainer) {
var hasSetting = localSettings.containers["MySettingsContainer"].values.hasKey("Title");
if (hasSetting) {
var title = localSettings.containers.lookup("MySettingsContainer").values["Title"];
}
}
Per eliminare i dati, semplici o complessi, dallo storage, è sufficiente invocare il metodo Remove
, come illustrato qui di seguito:
localSettings.values.remove("MySettings");
Per cancellare un container, invece, la classe ApplicationDataContainer
espone il metodo DeleteContainer
:
localSettings.deleteContainer("MySettingsContainer");
È importante ricordare che, complessivamente, i dati salvati nei LocalSettings
non possono superare il limite di 1 MB (al momento della scrittura di questo articolo). Se i dati applicativi eccedono questo limite, è sempre possibile persistere i relativi valori in un file all'interno dello storage locale.
Ad esempio, se volessimo tenere traccia delle operazioni dell'utente tramite un sistema di logging, un file di testo potrebbe rappresentare una soluzione efficiente. Il seguente snippet mostra un esempio di log elementare:
function createLog_click(args) {
try {
var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
var formatter = new
Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");
storageFolder.createFileAsync("log.txt",
Windows.Storage.CreationCollisionOption.ReplaceExisting)
.done(function (file) {
Windows.Storage.FileIO.writeTextAsync(file, formatter.format(new Date()));
});
}
catch (ex) {
// Gestire l'eccezione
}
}
Il codice crea un file, denominato log.txt, e lo salva nello storage locale tramite il metodo asincrono CreateFileAsync
, quindi lo referenzia nel metodo WriteTextAsync
dell'oggetto FileIO
. L'enum Windows.Storage.CreateCollisionOption
può assumere i seguenti valori:
Valore | Descrizione |
---|---|
ReplaceExisting | crea un nuovo file; se esiste già un file con lo stesso nome, lo sovrascrive |
FailIfExist | restituisce un errore se un file con lo stesso nome esiste già |
OpenIfExists | se esiste già un file con lo stesso nome, lo apre, mentre se non esiste ne crea uno nuovo |
GenerateUniqueName | crea un nuovo file aggiungendo al nome un numero autogenerato, nel caso in cui esista già un file con lo stesso nome, in modo da evitare conflitti |
Per leggere il file dallo storage, usiamo il seguente codice:
function readLog_click(args) {
try {
var localFolder = Windows.Storage.ApplicationData.current.localFolder;
localFolder.getFileAsync("log.txt")
.then(function (logFile) {
return Windows.Storage.FileIO.readTextAsync(logFile);
})
.done(function (logDateTime) {
// Esegui operazioni su log e data
});
}
catch (ex) {
// Gestire l'eccezione
}
}
Per facilitare ulteriormente le operazioni di scrittura e lettura di un file, la libreria WinJS mette a disposizione l'oggetto WinJS.Application.local, nient'altro che un wrapper attorno alla classe ApplicationData
che astrae ulteriormente l'accesso allo storage locale. Il prossimo codice mostra come scrivere e leggere un file nello storage locale sfruttando, rispettivamente, le funzioni WinJS.Application.local.writeText
e WinJS.Application.local.readText
:
function writeSetting_click(args) {
try {
var formatter = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");
WinJS.Application.local.writeText("log.txt", formatter.format(new Date())).done();
}
catch (ex) {
// gestire eccezione
}
}
function readSetting_click(args) {
try {
WinJS.Application.local.exists("log.txt")
.done(
function (exists) {
if (exists) {
WinJS.Application.local.readText("log.txt").done(function (content)
{
var logTime = content;
});
}
else {
// il file non esiste
}
}
);
}
catch (ex) {
// gestire l'eccezione
}
}
Lo storage di roaming
Anziché salvare i dati applicativi nello storage locale, è possibile utilizzare il roaming per mantenere le impostazioni applicative automaticamente sincronizzate su device diversi. Sarà il sistema a gestire il processo di sincronizzazione, tenendo conto anche dello stato della batteria e del consumo di banda.
Come per lo storage locale, se c'è bisogno di salvare dati applicativi complessi, si può sfruttare la classe ApplicationDataCompositeValue
per comporre la struttura dei tuoi dati. Il seguente codice mostra un esempio di salvataggio dei dati nello storage roaming. Come possiamo osservare, il codice è praticamente identico a quello utilizzato per lo storage locale:
function saveAppData_click(args) {
var roamingSettings = Windows.Storage.ApplicationData.current.roamingSettings;
var composite = new Windows.Storage.ApplicationDataCompositeValue();
composite["Title"] = "Html.it Demo";
composite["Content"] = "Contenuto d'esempio";
roamingSettings.values["MySettings"] = composite;
}
function retrieveAppData_click(args) {
var roamingSettings = Windows.Storage.ApplicationData.current.roamingSettings;
var composite = roamingSettings.values["MySettings"];
if (composite == null) {
// Nessun dato disponibile
}
else {
var title = composite["Title"];
var content = composite["Content"];
}
}
Come per lo storage locale, anche in questo caso è possibile raggruppare le impostazioni applicative in container per renderne più semplice la gestione. Il prossimo snippet illustra questo punto:
function saveAppDataContainer_click(args) {
var roamingSettings = Windows.Storage.ApplicationData.current.roamingSettings;
var container = roamingSettings.createContainer("MySettingsContainer", Windows.Storage.ApplicationDataCreateDisposition.always);
if (roamingSettings.containers.hasKey("MySettingsContainer")) {
roamingSettings.containers.lookup("MySettingsContainer").values["Title"] = "Html.it Demo";
}
}
Il codice crea un container utilizzando il metodo CreateContainer
, passando come parametro il nome del container e un valore dell'enum ApplicationDataCreateDisposition
, già incontrato a proposito dello storage locale. Dopo aver verificato che il container sia stato creato, il codice salva il valore applicativo nello storage di roaming. Il seguente snippet mostra invece come recuperare dallo storage il valore salvato in precedenza:
function retrieveAppDataContainer_click(args) {
var roamingSettings = Windows.Storage.ApplicationData.current.roamingSettings;
var hasContainer = roamingSettings.containers.hasKey("MySettingsContainer");
if (hasContainer) {
var hasSetting = roamingSettings.containers["MySettingsContainer"].values.hasKey("Title");
if (hasSetting) {
var title = roamingSettings.containers.lookup("MySettingsContainer").values["Title"];
}
}
}
Per cancellare un container, è possibile usare il metodo DeleteContainer
dell'oggetto di tipo ApplicationDataContainer
:
roamingSettings.deleteContainer("MySettingsContainer");
Come per lo storage locale, anche in quello di roaming è possibile creare un file per salvare strutture dati particolarmente complesse o che eccedono i limiti di quota previsti per le impostazioni applicative. Tuttavia, mentre per lo storage locale non sussistono limitazioni alle dimensioni del file, nel caso del roaming le dimensioni non possono superare la soglia specificata dalla proprietà RoamingStorageQuota
della classe ApplicationData
. Se questo limite viene superato, la sincronizzazione non viene effettuata.
Il seguente snippet mostra un esempio di salvataggio delle impostazioni applicative in un file all'interno dello storage di roaming. Come si può notare, ancora una volta il codice è pressoché identico a quello usato per lo storage locale:
function createLog_click(args) {
try {
var storageFolder = Windows.Storage.ApplicationData.current.roamingFolder;
var formatter = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");
storageFolder.createFileAsync("log.txt", Windows.Storage.CreationCollisionOption.ReplaceExisting)
.done(function (file) {
Windows.Storage.FileIO.writeTextAsync(file, formatter.format(new Date()));
});
}
catch (ex) {
// Gestire l'eccezione
}
}
Il codice seguente mostra invece come recuperare il file dallo storage di roaming:
function readLog_click(args) {
try {
var storageFolder = Windows.Storage.ApplicationData.current.roamingFolder;
storageFolder.getFileAsync("log.txt")
.then(function (logFile) {
return Windows.Storage.FileIO.readTextAsync(logFile);
})
.done(function (logDateTime) {
// Esegui operazioni su log e data
});
}
catch (ex) {
// Gestire l'eccezione
}
}
Nel caso dello storage roaming, è inoltre possibile abbonarsi all'evento DataChanged, esposto dalla classe ApplicationData
, per ricevere una notifica ogniqualvolta i dati nel profilo di roaming dell'utente vengono modificati; in questo modo è possibile reagire ai cambiamenti (ad esempio, aggiornando la user interface):
Windows.Storage.ApplicationData.current.ondatachanged = dataChangedHandler;
function dataChangedHandler(appData){
...
}
Anche per quanto riguarda lo storage di roaming, WinJS mette a disposizione due wrapper che semplificano ulteriormente la scrittura e la lettura di file all'interno dello storage di roaming tramite i metodi esposti dall'oggetto WinJS.Application.roaming
. Il seguente codice illustra questo punto:
function writeSetting_click(args) {
try {
var formatter = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");
WinJS.Application.roaming.writeText("log.txt", formatter.format(new Date())).done();
}
catch (ex) {
// gestire eccezione
}
}
function readSetting_click(args) {
try {
WinJS.Application.local.exists("log.txt")
.done(
function (exists) {
if (exists) {
WinJS.Application.roaming.readText("log.txt")
.done(function (content) {
var logTime = content;
});
}
else {
// il file non esiste
}
}
);
}
catch (ex) {
// gestire l'eccezione
}
}
Quando si lavora con lo storage roaming, è importante tenere in considerazione le linee guida di Microsoft. In primo luogo, lo storage roaming dovrebbe essere usato solo per quelle impostazioni che possono essere effettivamente riutilizzate su un device diverso, come ad esempio colori di sfondo, punteggi, dati statistici o l'ultima pagina letta di un ebook. È invece sconsigliabile utilizzare il profilo roaming per sincronizzare preferenze che possono variare a seconda del device: ad esempio, non avrebbe molto senso salvare il numero di elementi visualizzabili in una lista, poiché lo schermo di un PC desktop non è la stessa cosa di un tablet.
Tieni inoltre presente che Windows 8 trasferisce i dati in roaming da un device all'altro tenendo conto di numerosi fattori, in modo da determinare il momento migliore per effettuare la sincronizzazione. Inoltre, per poter sfruttare il roaming, è necessario che l'utente abbia un account Microsoft, che abbia effettuato il login sul device e che quest'ultimo sia stato marcato come "trusted".
Lo storage temporaneo
Se la nostra app ha bisogno di salvare dati temporanei, è possibile utilizzare lo storage temporaneo messo a disposizione da WinRT. Il codice è simile a quello già visto per le altre due tipologie di storage (locale e roaming), cambiando unicamente la classe necessaria a ottenere una reference allo storage:
var tmpFolder = Windows.Storage.ApplicationData.current.temporaryFolder;
Per il resto, il codice è del tutto identico a quello visto in precedenza.
Tieni presente che lo storage temporaneo non ha limiti di dimensioni, a parte i limiti fisici del filesystem.
IndexedDB
Un'altra opzione a disposizione di un'applicazione Windows Store in HTML5/JavaScript per persistere coppie chiave/valore è offerta dalla tecnologia IndexedDB. Questa si basa sul sistema Indexed Sequential Access Method (ISAM), all'interno del quale i dati sono suddivisi in record composti da campi a lunghezza fissa. I record sono immagazzinati in sequenza ed esiste un set di tabelle di hash che contengono puntatori ai singoli record, in modo da poter recuperare il record desiderato senza dover scorrere tutto l'elenco sequenzialmente. Questo tipo di storage presenta alcuni limiti, che, al momento della scrittura di questo articolo, sono :
- un limite di 250 MB per applicazione;
- un limite complessivo di 375 MB per tutte le app, se la dimensione dell'hard disk è inferiore a 30 GB; se invece le dimensioni eccedono i 30 GB, il limite complessivo è del 4% della capacità, con un limite massimo di 20 GB;
- Le URL delle pagine devono essere incluse nella sezione
ApplicationContentUriRules
dell'application manifest (corrispondente al tab Content URIs, se usi il designer di Visual Studio), a meno che il meta tag "ms-enable-external-database-usage" sia presente nella home page dell'applicazione.
Il seguente snippet mostra un esempio di accesso all'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);
}
}
Altre opzioni disponibili
Qualunque applicazione Windows Store scritta in HTML5/JavaScript Any HTML può sfruttare l'HTML5 Web Storage API (lo stesso può dirsi per le app in C#/VB o C++ che utilizzano il controllo WebView
). Queste API permettono di persistere coppie chiave/valore utilizzando gli standard web. Lo storage è isolato per singolo utente e per ciascuna applicazione, in modo da evitare conflitti. Il limite è di 10 MB per ciascun utente.
In particolare, grazie all'oggetto localStorage è possibile persistere dati localmente nello storage in un contesto web; per dati temporanei, destinati ad essere mantenuti solo in memoria e a non sopravvivere alla chiusura dell'applicazione, è possibile usare la classe sessionStorage
.
Infine, WinJS mette a disposizione l'oggetto WinJS.Application.sessionState
, ossia un wrapper attorno alle API ApplicationData
che permette di salvare stati applicativi transitori durante la fase di sospensione, per poi recuperarli al riavvio dell'applicazione. Questo oggetto supporta strutture dati definite dall'applicazione (dunque non semplicemente coppie chiave/valore), poiché, dietro le quinte, utilizza lo storage locale per persistere la struttura dati all'interno di un file in formato JSON (denominato "_sessionState.json").
Il prossimo snippet mostra la definizione completa dell'oggetto WinJS.Application.sessionState
all'interno del file base.js:
WinJS.Namespace.define("WinJS.Application", {
sessionState: {},
_loadState: function (e) {
var app = WinJS.Application;
// we only restore state if we are coming back from a clear termination from PLM
//
if (e.previousExecutionState === 3 /* ApplicationExecutionState.Terminated */) {
return app.local.readText("_sessionState.json", "{}").
then(function (str) {
var sessionState = JSON.parse(str);
if (sessionState && Object.keys(sessionState).length > 0) {
app._sessionStateLoaded = true;
}
app.sessionState = sessionState;
}).
then(null, function (err) {
app.sessionState = {};
});
}
else {
return WinJS.Promise.as();
}
},
_oncheckpoint: function (e) {
if (global.MSApp && MSApp.getViewOpener && MSApp.getViewOpener()) {
// don't save state in child windows.
return;
}
var app = WinJS.Application;
var sessionState = app.sessionState;
if ((sessionState && Object.keys(sessionState).length > 0) || app._sessionStateLoaded) {
var stateString;
try {
stateString = JSON.stringify(sessionState)
} catch (e) {
stateString = "";
WinJS.Application.queueEvent({ type: "error", detail: e });
}
e.setPromise(
app.local.writeText("_sessionState.json", stateString).
then(null, function (err) {
app.queueEvent({ type: "error", detail: err });
})
);
}
}
});
Come si può vedere, l'oggetto incapsula la logica necessaria per serializzare la struttura dati al momento della sospensione e per recuperare la stessa alla riattivazione dell'applicazione.
Persistere user data
Anche per persistere user data le opzioni a disposizione di un'app Windows Store in HTML5/JavaScript sono molteplici, come le librerie dell'utente, OneDrive (precedentemente conosciuto come SkyDrive), web standard come le HTML5 File API o le HTML5 Application Cache API, oppure servizi esterni, database di terze parti e servizi cloud come Windows Azure Storage.
Per quanto riguarda le librerie utente, le API di WinRT permettono di salvare e recuperare file da qualunque app Windows Store, a prescindere dal linguaggio utilizzato. La classe Windows.Storage.StorageFile
e il controllo Windows.Storage.Pickers.FileOpenPicker
consentono di accedere alle librerie in modo semplice e immediato (ricorda che per l'accesso programmatico alle librerie utente è necessario aggiungere le corrispondenti "capability" nell'application manifest della tua app). I dati salvati nelle librerie utente sopravvivono all'applicazione e sono indipendenti dall'utente che esegue la stessa (questi argomenti verranno approfonditi nel prossimo articolo della guida).
È anche possibile usare OneDrive per salvare gli user data nel cloud, eventualmente condividendoli fra più applicazioni e piattaforme. Considera che OneDrive (così come il Windows Azure Storage Service) non è un database relazionale, ma un servizio di storage condiviso. Per conoscere le API esposte da OneDrive, puoi consultare la documentazione ufficiale MSDN.
Infine, tieni presente che Windows 8 non fornisce alcun database locale nativo per applicazioni Windows Store, né mette a disposizione API per accedere a un database. Non è infatti possibile accedere direttamente SQL Azure, o un'istanza in locale di SQL Server o SQL Express. Per superare queste limitazioni, è possibile avvalersi di soluzioni di terze parti, oppure appoggiarsi a servizi web SOA/REST come ponte verso un database relazionale.