Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Persistenza e caching in locale

Link copiato negli appunti

WinRT offre numerosi modi per persistere i dati, ciascuno con i propri vantaggi e svantaggi. La scelta del meccanismo di persistenza finisce così per dipendere dal contesto applicativo, oltre che dal linguaggio scelto per implementare l'app.

Application Data e User Data

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 un database, o in uno storage non relazionale, o su filesystem. 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 più app o più utenti. Inoltre, se l'utente aggiorna l'app, i dati applicativi devono essere preservati, mentre se l'utente disinstalla l'app, i dati devono essere rimossi. Per fortuna, WinRT espone meccanismi di storage che soddisfano tutti questi requisiti. Tieni presente che non è possibile sfruttare questo stesso meccanismo per user data, come entità applicative o documenti: per questi, puoi usare le librerie utente, 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 e WinJS) 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 comporli per formare tipi complessi, da gestire tramite operazioni atomiche. Il prossimo snippet mostra un esempio di tipo composto:

ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue();
composite["FirstItem"] = "The answer is";
composite["SecondItem"] = 42;

Tieni presente 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:

ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
StorageFolder localFolder = ApplicationData.Current.LocalFolder;

Il seguente codice mostra come salvare e rileggere dati applicativi complessi:

public void SaveApplicationData_Click(Object sender, RoutedEventArgs e)
{
    ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
    ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue();
    composite["Title"] = "Html.it Demo";
    composite["Content"] = "Contenuto d'esempio";
    localSettings.Values["MySettings"] = composite;
}
public void RetrieveApplicationData_Click(Object sender, RoutedEventArgs e)
{
    ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
    var composite = (Windows.Storage.ApplicationDataCompositeValue)localSettings.Values["MySettings"];
    if (composite == null)
    {
        // Nessun dato disponibile
    }
    else
    {
        Object title = composite["Title"];
        Object 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'istanza 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:

ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
ApplicationDataContainer container = localSettings.CreateContainer("MySettingsContainer", ApplicationDataCreateDisposition.Always);
if (localSettings.Containers.ContainsKey("MySettingsContainer"))
{
    localSettings.Containers["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 il valore "Html.it Demo" nella chiave denominata Title.

Per ottenere una reference al container e recuperare i dati applicativi, invece, è possibile sfruttare l'enum Containers che ne verificare l'esistenza (tramite il metodo ContainsKey) e, in caso positivo, recuperare i valori salvati in precedenza.

ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
Boolean hasContainer = localSettings.Containers.ContainsKey("MySettingsContainer");
if (hasContainer)
{
    Boolean hasSetting = localSettings.Containers["MySettingsContainer"].Values.ContainsKey("Title");
    if (hasSetting)
    {
        var title = localSettings.Containers["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");

Ricordiamo che, complessivamente, i dati salvati nei LocalSettings non possono superare il limite di 1 MB (al momento della scritta di questo articolo). Se i tuoi 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 più efficiente. Il seguente snippet mostra un esempio di log elementare:

private async void CreateLog_Click(object sender, RoutedEventArgs e)
{
    try
    {
        StorageFolder localFolder = ApplicationData.Current.LocalFolder;
        var formatter = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");
        StorageFile logFile = await localFolder.CreateFileAsync("log.txt", CreationCollisionOption.ReplaceExisting);
        await FileIO.WriteTextAsync(logFile, formatter.Format(DateTime.Now));
    }
    catch (Exception 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 della classe FileIO. L'enum 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, puoi usare il seguente codice:

private async void ReadLog_Click(Object sender, RoutedEventArgs e)
{
    try
    {
        StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
        StorageFile logFile = await localFolder.GetFileAsync("log.txt");
        String logDateTime = await FileIO.ReadTextAsync(logFile);
        // Esegui operazione su log e data
    }
    catch (Exception ex)
    {
        // Gestire l'eccezione
    }
}

Lo storage 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 hai bisogno di salvare dati applicativi complessi, puoi 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 puoi osservare, il codice è praticamente identico a quello utilizzato per lo storage locale, tranne il nome della proprietà RoamingSettings:

public void SaveAppData_Click(Object sender, RoutedEventArgs e)
{
    ApplicationDataContainer roamingSettings = ApplicationData.Current.RoamingSettings;
    ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue();
    composite["Title"] = "Html.it Demo";
    composite["Content"] = "Contenuto d'esempio";
    roamingSettings.Values["MySettings"] = composite;
}
public void RetrieveAppData_Click(Object sender, RoutedEventArgs e)
{
    ApplicationDataContainer roamingSettings = ApplicationData.Current.RoamingSettings;
    var composite = (Windows.Storage.ApplicationDataCompositeValue)roamingSettings.Values["MySettings"];
    if (composite == null)
    {
        // Nessun dato disponibile
    }
    else
    {
        Object title = composite["Title"];
        Object 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:

public void SaveAppDataContainer_Click(object sender, RoutedEventArgs e)
{
    ApplicationDataContainer roamingSettings = ApplicationData.Current.RoamingSettings;
    ApplicationDataContainer container = roamingSettings.CreateContainer("MySettingsContainer", ApplicationDataCreateDisposition.Always);
    if (roamingSettings.Containers.ContainsKey("MySettingsContainer"))
    {
        roamingSettings.Containers["MySettingsContainer"].Values["Title"] = "Html.it Demo";
    }
}

Il codice crea un container utilizzando il metodo CreateContainer, passando come parametro il nome del container e un'istanza 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:

public void RetrieveAppDataContainer_Click(object sender, RoutedEventArgs e)
{
    ApplicationDataContainer roamingSettings = ApplicationData.Current.RoamingSettings;
    Boolean hasContainer = roamingSettings.Containers.ContainsKey("MySettingsContainer");
    if (hasContainer)
    {
        Boolean hasSetting = roamingSettings.Containers["MySettingsContainer"].Values.ContainsKey("Title");
        if (hasSetting)
        {
            var title = roamingSettings.Containers["MySettingsContainer"].Values["Title"];
        }
    }
}

Per cancellare un container, è possibile usare il metodo DeleteContainer della classe ApplicationDataContainer:

roamingSettings.DeleteContainer("MySettingsContainer");

Come per lo storage locale, anche nello storage 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 roaming. Come si può notare, ancora una volta il codice è pressoché identico a quello usato per lo storage locale:

private async void CreateLog_Click(object sender, RoutedEventArgs e)
{
    try
    {
        StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder;
        var formatter = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");
        StorageFile logFile = await roamingFolder.CreateFileAsync("log.txt", CreationCollisionOption.ReplaceExisting);
        await FileIO.WriteTextAsync(logFile, formatter.Format(DateTime.Now));
    }
    catch (Exception ex)
    {
        // Gestire l'eccezione
    }
}

Nel caso dello storage roaming, è 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 modo da poter reagire ai cambiamenti (ad esempio, aggiornando la user interface):

ApplicationData.Current.DataChanged += new TypedEventHandler<ApplicationData, object>(DataChanged);
// ...
private void DataChanged(ApplicationData appData, object o)
{
   // aggiornare i dati o informare l’utente.
}

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 ha la stessa dimensione di un tablet.

Ricordiamo inoltre che Windows 8 trasferisce i dati 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:

StorageFolder tmpFolder = 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.

Persistere user data

Anche per persistere user data le opzioni a disposizione di un'app Windows Store in C# sono molteplici, come le librerie dell'utente, OneDrive (precedentemente noto come SkyDrive), 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 OneDrvie (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.

Ti consigliamo anche