In questa lezione estendiamo la nostra rubrica, aggiungendo operazioni più avanzate nella gestione dei contatti, come l'aggiornamento e rimozione delle entità, la validazione centralizzata dei dati lato back-end, o la paginazione delle query.
Aggiornamento e rimozione di data elements
La prima funzionalità che aggiungiamo è quella di modifica e rimozione dei contatti esistenti nella Rubrica. Per mantenere l'esempio più semplice possibile, ipotizziamo che nella nostra Rubrica non possano esserci nomi duplicati. Per questo, ogni volta che l'utente prova ad inserire un nuovo Contatto e il nome esiste già nella Rubrica, semplicemente andiamo ad aggiornare il numero di telefono del Contatto esistente.
Implementare questa funzionalità è estremamente semplice. Aggiungeremo prima il codice necessario a riconoscere il caso di contatto già esistente, poi aggioreremo il numero di telefono e salveremo tutto nel data storage del nostro Mobile Service.
La funzione da modificare è AggiungiContatto, e il codice diventerà quindi simile al seguente:
private async void AggiungiContatto(Contatto Contatto)
{
var contattoEsistente = contatti.FirstOrDefault(c => c.Nome == Contatto.Nome);
if (contattoEsistente != null)
{
contattoEsistente.Numero = Contatto.Numero;
await tblContatti.UpdateAsync(contattoEsistente);
}
else
{
await tblContatti.InsertAsync(Contatto);
contatti.Add(Contatto);
}
}
Per ottenere anche un aggiornamento automatico del Contatto visualizzato dall'utente, implementiamo una versione estremamente semplificata delle funzionalità di INotifyPropertyChanged
. Questo permette di notificare all'interfaccia grafica che alcuni dati sono cambiati e che è quindi necessario aggiornare la visualizzazione per l'utente. Apriamo il codice della classe Contatto
, e specifichiamo che la classe implementa l'interfaccia INotifyPropertyChanged
:
public class Contatto : INotifyPropertyChanged
quindi aggiungiamo in cima alla classe anche le poche righe necessarie a gestire questo evento manualmente:
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string PropertyName)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
Infine non resta che richiamare questo metodo nella riga subito successiva all'aggiornamento dei dati sul Mobile Service, che diventa quindi:
...
await tblContatti.UpdateAsync(contattoEsistente);
contattoEsistente.NotifyPropertyChanged("Numero");
...
Lanciando l'applicazione da Visual Studio 2012 viene aperto l'emulatore di Windows Phone 8, che ci permette di testare il nuovo comportamento della nostra applicazione. Lo screenshot di seguito mostra alcuni contatti di prova inseriti nell'applicazione e come i dati vengono aggiornati quando viene inserito un Contatto con un nome già esistente. Chiudendo e riaprendo l'applicazione, è possibile vedere che le modifiche sono state effettivamente memorizzate sul Mobile Service.
Passiamo ora all'operazione di eliminazione di una entry dalla nostra Rubrica. Aggiungiamo al template che visualizza ogni Contatto nell'interfaccia grafica un nuovo pulsante da premere per eliminare un particolare elemento. Il codice, già esistente, dovrà essere modificato inserendo un nuovo elemento <Button/>
nel DataTemplate
:
<phone:LongListSelector Grid.Row="3" Grid.ColumnSpan="2" Name="lstContatti">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="X" Click="btnRemove_Click" /> <!--aggiungere qui il bottone-->
<TextBlock Text="{Binding Nome, Mode=TwoWay}" Margin="10,5" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Numero, Mode=TwoWay}" Margin="10,5" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
Il codice relativo al click su questo pulsante è il seguente:
private void btnRemove_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
Contatto contatto = btn.DataContext as Contatto;
RimuoviContatto(contatto);
}
E a questo punto non resta che implementare il metodo RimuoviContatto(), che si occuperà di effettuare l'operazione di cancellazione sul nostro Mobile Service:
private async void RimuoviContatto(Contatto Contatto)
{
await tblContatti.DeleteAsync(Contatto);
contatti.Remove(Contatto);
}
Lanciando nuovamente l'emulatore, possiamo testare che la nostra Rubrica effettivamente permette anche la rimozione dei Contatti ora.
Query avanzate con i Mobile Services
La prossima funzionalità che aggiungiamo all'applicazione è la paginazione dei dati. Per ottimizzare le performance è buona norma trasmettere all'utente i dati in blocchi separati, un po' per volta.
Gli Azure Mobile Services supportano nativamente questa possibilità, integrandosi perfettamente con le query LINQ che gli sviluppatori .NET sono abituati ad utilizzare e semplificando di conseguenza di molto il lavoro.
È interessante sottolineare che per default, i Mobile Services restituiscono fino a 50 elementi per volta per ogni query. Questo limite di paginazione automatico aiuta a proteggere il servizio da possibili overflow di dati, ma è possibili aumentarlo fino a 1000 elementi per ogni response impostando esplicitamente la dimensione di paginazione.
Al codice della nostra applicazione andiamo ad aggiungere una proprietà per tenere traccia della pagina correntemente visualizzata. Inoltre, serve definire una costante che specifichi quanti elementi vogliamo visualizzare per ogni pagina. Inseriamo quindi in cima alla classe MainPage
queste righe:
private int PaginaCorrente = 0;
private const int ITEMS_PER_PAGINA = 2;
Modifichiamo innanzitutto l'interfaccia utente della nostra applicazione in modo da mostrare due pulsanti per scorrere le pagine dei contatti avanti ed indietro. L'interfaccia è organizzata con una Grid, alla quale dobbiamo aggiungere una nuova riga sotto a quella che ospita la lista dei contatti. L'elemento RowDefinition
della Grid diventerà:
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
A questo punto aggiungiamo i pulsanti. Ecco il codice da aggiungere subito sotto al tag di chiusura dell'elemento <phone:LongListSelector />
:
<Button Grid.Row="4" Grid.Column="0" Content="<" Click="btnPaginaPrecedente_Click" />
<Button Grid.Row="4" Grid.Column="1" Content=">" Click="btnPaginaSuccessiva_Click" />
L'interfaccia dell'applicazione ora appare in questo modo:
I due pulsanti aggiunti nel codice XAML hanno già pronto il riferimento ai metodi, nel codice della finestra, che si dovranno occupare del paging. I metodi che dovremo quindi aggiungere nel codice sono i seguenti:
private void btnPaginaPrecedente_Click(object sender, RoutedEventArgs e)
{
LoadContatti(PaginaCorrente - 1);
}
private void btnPaginaSuccessiva_Click(object sender, RoutedEventArgs e)
{
LoadContatti(PaginaCorrente + 1);
}
Il codice richiama la funzione già esistente LoadContatti()
, che però nella versione attuale carica tutti i Contatti e non richiede quindi alcun parametro. Modifichiamo quindi quel metodo con il seguente codice:
private void LoadContatti(int Pagina = 0)
{
if (Pagina < 0) Pagina = 0;
contatti = tblContatti.Skip(Pagina * ITEMS_PER_PAGINA)
.Take(ITEMS_PER_PAGINA)
.ToCollectionView();
lstContatti.ItemsSource = contatti;
this.PaginaCorrente = Pagina;
}
Questo metodo ora si assicura innanzitutto che il numero di pagina richiesto sia valido (premendo il tasto <
dalla prima pagina, verrebbe altrimenti richiesta la pagina di indice -1, causando un'eccezione). A questo punto sfrutta i metodi Skip()
e Take()
di LINQ per caricare solo gli elementi desiderati.
Dietro le quinte, l'URI della richiesta che viene effettuata al nostro Azure Mobile Service viene tradotta aggiungendo i parametri $top
e $skip
; è possibile dargli un'occhiata utilizzando uno dei vari strumenti free che permettono di intercettare le chiamate di rete, come ad esempio Fiddler.
Ecco quindi come appare la nostra applicazione conclusa, che mostra solo 2 elementi per pagina e permette ora la modifica e la cancellazione di Contatti.
Validazione dei dati lato back-end
L'ultima funzione di gestione avanzata dei dati che aggiungiamo al servizio è la validazione dei dati tramite gli script lato back-end, chiamati "server scripts". Questi script sono registrati all'interno del Mobile Service e vengono lanciati automaticamente dal servizio all'occorrere di particolari eventi, come una nuova operazione di inserzione, modifica o cancellazione di dati da parte di un client. Le operazioni che è possibile effettuare nei server script sono molte, come ad esempio la modifica dinamica dei dati o, appunto, la validazione.
L'utilità di una tale tecnica è quella di centralizzare il codice di validazione dei dati in un unico punto, ed evitare quindi problemi di manutenibilità per applicazioni che sono sviluppate per molte piattaforme differenti.
Creiamo uno script che per verificare che la lunghezza del nome di un nuovo Contatto sia sempre compresa fra minimo 1 e massimo 25 caratteri. Per fare ciò, accediamo al Portale di Gestione di Windows Azure , navighiamo fino alla pagina di dettaglio del nostro mobile service "Rubrica" e spostiamoci nella scheda "Dati". Clicchiamo quindi sul link della nostra tabella "Contatto" e scegliamo la scheda "Script".
Come è possibile vedere, sono presenti già degli script preconfigurati e tramite la casella a discesa è possibile verificare i dettagli di ognuno, rispettivamente "Inserisci", "Modifica", "Elimina" e "Leggi". È interessante notare come l'editor integrato all'interno del Portale di Gestione offra anche funzionalità istantanee di rilevamento degli errori di sintassi e syntax highlighting.
Modifichiamo direttamente dal browser lo script di inserimento validando la proprietà "nome" del nuovo Contatto che il cliente del nostro servizio sta cercando di inserire:
function insert(item, user, request) {
if (item.nome.length <1 item.nome.length > 25) {
request.respond(statusCodes.BAD_REQUEST, 'Il nome del Contatto deve avere fra 1 e 25 caratteri.');
} else {
request.execute();
}
}
Se il nome del nuovo Contatto non è della lunghezza desiderata, quindi, il servizio restituirà uno degli status code predefiniti di HTTP per rappresentare una richiesta non valida, e ritorna anche un messaggio d'errore esplicativo.
Salviamo lo script con il tasto Save in basso alla pagina Web, e abbiamo terminato la parte server della validazione.
Non rimane quindi che aggiornare la nostra applicazione mobile Windows Phone 8 per essere in grado di gestire gli eventuali messaggi di errore che la validazione del server script del Mobile Service potrebbe inviare. Modifichiamo quindi il metodo AggiungiContatto() in questo modo:
private async void AggiungiContatto(Contatto Contatto)
{
var contattoEsistente = contatti.FirstOrDefault(c => c.Nome == Contatto.Nome);
if (contattoEsistente != null)
{
contattoEsistente.Numero = Contatto.Numero;
await tblContatti.UpdateAsync(contattoEsistente);
contattoEsistente.NotifyPropertyChanged("Numero");
}
else
{
try
{
await tblContatti.InsertAsync(Contatto);
contatti.Add(Contatto);
}
catch (MobileServiceInvalidOperationException e)
{
MessageBox.Show(e.Response.Content);
}
}
}
È da notare che la libreria di accesso agli Azure Mobile Services per Windows Phone traduce automaticamente gli errori ritornati dal servizio mobile sul Cloud in eccezioni non gestite. In particolare, l'error response HTTP 400 Bad Request
viene tradotta in un'eccezione MobileServiceInvalidOperationException
.