Nella lezione precedente abbiamo effettuato il setup di un servizio mobile di Windows Azure e creato una tabella per i nostri dati. Il passo seguente è quello di creare l'applicazione iOS tramite la quale interagire con il servizio che abbiamo costruito in modo da leggere/scrivere dati all'interno della tabella.
Molte delle operazioni che effettueremo in questa fase sono meglio dettagliate nella guida "Creare applicazioni iPhone e iPad.
>> Leggi come Creare applicazioni iPhone e iPad
Download dell'SDK
Per prima cosa scarichiamo l'SDK di Windows Azure. Nella pagina di download sceglieremo naturalmente la versione iOS.
Import del framework all'interno del progetto Xcode
Una volta scaricato il file dovremo importarlo all'interno di Xcode dentro alla cartella dei Frameworks. Essendo un framework potremo quindi ispezionare solo gli header dei file per capire quali metodi e proprietà mettono a disposizione, ma non potremo chiaramente visionare i file di implementazione .m
.
A questo punto creiamo un view controller, impostiamolo come rootViewController
della window
(nel metodo application: didFinishLaunchingWithOptions:
) e spostiamoci nel suo file di implementazione .m
.
Infine importiamo il framework con la seguente direttiva:
#import < WindowsAzureMobileServices/WindowsAzureMobileServices.h >
e saremo pronti per interrogare la tabella appena creata.
MSClient e MSTable
Una volta montato l'SDK, posizioniamoci all'interno del metodo di init
e dichiariamo un oggetto di tipo MSClient
che ci permetterà di interagire con il servizio web.
L'oggetto MSClient è il punto di partenza per la comunicazione con i servizi mobile di Windows Azure. Questo oggetto ci consente di creare successivamente un'istanza dell'oggetto MSTable che viene usato per manipolare i dati presenti all'interno delle tabelle del servizio web.
MSClient *client = [MSClient clientWithApplicationURLString:@"https://articles.azure-mobile.net/"
withApplicationKey:@"cczdHhxDHtVxFgkMnuzGpiiBUiuusM62"];
Analizziamo un attimo la linea di codice appena scritta. Abbiamo creato un oggetto MSClient
con due parametri:
URLString
che è l'indirizzo del servizio che abbiamo creato nella prima lezione;ApplicationKey
che è una chiave univoca con la quale il sistema può autenticare l'applicazione iOS quando cercherà di effettuare delle connessioni al servizio.
A questo punto dobbiamo crearci un riferimento (reference) alla tabella con la quale dovremo poi interagire tramite un oggetto di tipo MSTable
.
Nel codice, la classe MSTable rappresenta una tabella dei servizi mobile di Windows Azure. Tramite questa classe è possibile inserire, modificare, cancellare e leggere dati dalla tabella. Sulla tabella è inoltre possibile eseguire delle query tramite l'oggetto di sistema NSPredicate
che ritornerà le righe della tabella che soddisfano le condizioni del predicato.
Tutte le operazioni sulle tabella generano una richiesta HTTP a Windows Azure.
MSTable * table = [client getTable:@"articleData"];
L'unico parametro da passare è il nome della tabella.
Vediamo ora come eseguire sulla tabella le operazioni di inserimento, modifica, cancellazione e lettura di dati.
Inserimento dati
Come detto in precedenza, le colonne della tabella non possono essere aggiunte dal Management Portal. Per aggiungere i campi alla tabella di Windows Azure dobbiamo predisporre all'interno dell'applicazione, un dizionario che contenga le coppie "nomeColonna" : "valore"
, per ciascuna colonna.
Ciò significa che la prima volta che effettueremo un inserimento, la tabella conterrà solo la colonna id
e che dopo l'inserimento saranno aggiunte automaticamente le nuove colonne a partire dalle chiavi del dizionario.
Se poi effetuiamo un nuovo inserimento e nel dizionario avremo specificato un ulteriore campo, otterremo come effetto quello di aggiungere alla tabella la nuova colonna, con l'unico inconveniente che tutte le righe inserite precedentemente avranno come valore della colonna NULL
.
Per fare un esempio supponiamo di voler aggiungere alla tabella la colonna Nome
e di voler inserire una riga con valore Michelangelo
in quella colonna. Il nostro dizionario sarà definito in questo modo:
NSDictionary *item = @{ @"Nome" : @"Michelangelo"};
A questo punto siamo finalmente pronti per effettuare l'inserimento sfruttando il metodo insert:completion: sull'oggetto table
:
[table insert:item completion:^(NSDictionary *insertedItem, NSError *error)
{
if (error)
{
NSLog(@"Error: %@", error);
} else
{
NSLog(@"Item inserted, id: %@", [insertedItem objectForKey:@"id"]);
}
}];
Se eseguiamo questo codice e poi andiamo sul portale avremo la tabella nel seguente stato:
È molto importante ricordare che nel dizionario item
non deve essere presente la chiave id
dal momento che questo campo è di tipo autoincrementante e rappresenta la chiave univoca tramite la quale distinguere una riga dalle altre ed è quindi gestito direttamente dal servizio.
Supponiamo ora di voler aggiungere alla tabella anche le colonne per cognome e anno di nascita: aggiungeremo un nuovo record contenente oltre al campo Nome
, anche il campo Cognome
ed il campo Anno
:
NSDictionary *item = @{
@"Nome" : @"Leonardo",
@"Cognome" : @"Da Vinci",
@"Anno" : @1452 };
[table insert:item completion:^(NSDictionary *insertedItem, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Item inserted, id: %@", [insertedItem objectForKey:@"id"]);
}
}];
Per decidere il tipo di dato da inserire in una colonna è sufficiente stare attenti a come passiamo il parametro al servizio la prima volta che creiamo la colonna. In questo caso, il campo Anno
è ovviamente un numero e quindi viene codificato all'interno del dizionario come un numero e non tramite una stringa per i campi Nome
e Cognome
.
Inoltre possiamo notare come il record con id = 1
, inserito quando le colonne Cognome
e Anno
non erano presenti, contenga per tali campi il valore NULL
. Per valorizzarli, dovremo usare la funzione di update che vedremo tra poco.
Modifica dati
Vediamo ora come modificare i parametri all'interno di una riga (ad esempio la riga con id = 1
). Faremo qualcosa di molto simile a quanto visto per l'inserimento, con l'unica differenza che nel dizionario item
dovrà essere presente la chiave id
contenente l'id della riga che vogliamo modificare.
Creiamo un dizionario contenente oltre alle chiavi Nome
e Cognome
anche la chiave id
:
NSDictionary *item = @{
@"id" : @1,
@"Cognome" : @"Buonarroti",
@"Anno" : @1475
};
[table update:item completion:^(NSDictionary *item, NSError *error) {
if (error) {
NSLog(@"%@", error.localizedDescription);
} else {
NSLog(@"Item updated, id: %@", [item objectForKey:@"id"]);
}
}];
Il campo id
essendo un campo di tipo numerico deve essere passato come un numero altrimenti, se passato come stringa genererà un errore in quanto il server non riuscirà a trovare la riga con l'id specificato.
A questo punto tornando sul Management Portal nella pagina relativa ai dati contenuti all'interno della tabella, potremo vedere il seguente risultato:
Cancellazione dati
La cancellazione è del tutto analoga alla modifica, ma con la differenza che il dizionario dovrà contenere solo l'id della riga che vogliamo cancellare.
NSDictionary *item = @{
@"id" : @3
};
[table delete:item completion:^(NSNumber *itemId, NSError *error) {
NSLog(@"Item deleted, id: %i", [itemId intValue]);
}];
Se torniamo ad ispezionare la tabella, questa conterrà ovviamente solo un record: ovvero quello con id = 1
.
Lettura dei dati dalla tabella
La funziona di lettura dei dati ci permette di eseguire quella che in linguaggio SQL sarebbe una SELECT
per poter fruire dei dati presenti nella tabella.
Facciamo prima qualche inserimento nella tabella in modo da avere qualche record in più e portiamo la tabella in uno stato simile a questo:
Abbiamo diversi metodi da poter sfruttare per la lettura a seconda dei casi che ci si presentano. Di seguito esaminiamo i più comuni:
Leggere tutti i dati della tabella
Il caso più semplice di tutti è quello in cui chiediamo di ottenere tutti i dati contenuti all'interno della tabella tramite il metodo readWithCompletion:
[table readWithCompletion:^(NSArray *items, NSInteger totalCount, NSError *error) {
if (error)
{
NSLog(@"%@", error.localizedDescription);
}
else
{
for (NSDictionary * anItem in items)
{
NSLog(@"%@", anItem);
}
}
}];
Che stamperà in console qualcosa come:
{ Anno = 1475; Cognome = Buonarroti; Nome = Michelangelo; id = 1; }
{ Anno = 1265; Cognome = Alighieri; Nome = Dante; id = 11; }
{ Anno = 1798; Cognome = Leopardi; Nome = Giacomo; id = 12; }
{ Anno = 1451; Cognome = Colombo; Nome = Cristoforo; id = 13; }
I dati sono restituiti sotto forma di array di dizionari, ovvero ogni oggetto all'interno dell'array è un dizionario che rappresenta una riga della tabella.
Specificare l'id della singola riga
Possiamo specificare l'id
della riga che vogliamo leggere e ottenere nel block di completion un NSDictionary
contenente una coppia (chiave, valore) per ogni colonna della tabella. Ad esempio, il seguente codice:
[table readWithId:@13 completion:^(NSDictionary *item, NSError *error) {
if (error)
{
NSLog(@"%@", error.localizedDescription);
}
else
{
NSLog(@"%@",item);
}
}];
produrrà come output:
{ Anno = 1451; Cognome = Colombo; Nome = Cristoforo; id = 13; }
Effettuare ricerche
Analizziamo ora una modalità che ci consente di effettuare vere e proprie ricerche nella tabella inserendo dei filtri tramite l'oggetto di sistema NSPredicate
:
NSPredicate * predicate = [NSPredicate predicateWithFormat:@"id = 12"];
[table readWhere:predicate completion:^(NSArray *items, NSInteger totalCount, NSError *error) {
if (error)
{
NSLog(@"%@", error.localizedDescription);
}
else
{
for (NSDictionary * anItem in items)
{
NSLog(@"%@", anItem);
}
}
}];
Il risultato sarà il seguente:
{ Anno = 1798; Cognome = Leopardi; Nome = Giacomo; id = 12; }
I Mobile Service Server Script
Come abbiamo visto, se siamo sul manager di Azure e dall'elenco delle tabelle ne selezioniamo una, cliccando col mouse, appare relativo pannello di configurazione. Tra le varie opzioni troviamo la voce del menu script.
Uno script è un blocco di codice che il sistema eseguirà automaticamente ogni volte che sarà effettuata un'operazione di inserimento, modifica, cancellazione o lettura sulla tabella selezionata. Cliccando sulla voce di menu script possiamo visualizzarne la struttura:
La pagina si apre di default sull'operazione di Insert per la quale è definita la seguente funzione JavaScript:
function insert(item, user, request) {
request.execute();
}
Ogni volta che, tramite l'applicazione iOS, effettuiamo un inserimento di una dato nella tabella, in maniera automatica, la piattaforma Windows Azure eseguirà questo script che di fatto inserirà materialmente i dati all'interno della tabella (analizzeremo tra poco il codice).
Ora dal menu a tendina Operazione, selezioniamo la voce modifica: la pagina si aggiornerà e sarà mostrato uno script, del tutto analogo, relativo alla modifica di alcuni dati della tabella (anche in questo caso avremo una sola riga di codice all'interno della funzione che esegue l'operazione di modifica).
Validare i dati
Ma come possono essere utilizzati questi script? Un classico esempio è la validazione dei valori dei campi prima dell'inserimento della tabella, oppure la validazione di un utente tramite username e password.
Vediamo quindi come definire uno script di insert che effettui una validazione dei valori inviati dall'applicazione iOS.
Creare uno script di validazione
Poiché le funzioni definite dagli script utilizzano, come parametri, degli oggetti definiti da Windows Azure, è possibile consultare documentazione relativa agli oggetti utilizzabili nei Mobile Service Server Script.
Analizziamo dunque gli oggetti principali che entrano in gioco nell'utilizzo di uno script (indipendentemente all'operazione a cui è associato):
Oggetto | Descrizione |
---|---|
item | rappresenta il set di dati che vogliamo inserire all'interno della tabella. Su tale oggetto sono definiti i metodi di get relativi alle colonne della tabella per consentirci di accedere ai valori che si desiderano inserire all'interno della tabella stessa |
user | rappresenta l'oggetto user relativo all'utente utenticato. Ovviamente se la chiamata al servizio mobile viene eseguita senza che sia stata fatta preventivamente un'autenticazione con un utente, questo parametro sarà vuoto |
request | rappresenta la request relativa all'operazione che vogliamo eseguire. Come possiamo vedere dal corpo della funzione insert l'oggetto request possiede il metodo execute che inserisce l'item all'interno della tabella |
Fatta questa brevissima panoramica sugli oggetti maneggiati dalla funzione di insert
, siamo pronti per validare i dati in ingresso ed in particolare andremo a controllare se i campi Nome
e Cognome
non sono vuoti e, in caso di errore, notificarlo all'applicazione. Posizioniamoci dunque nel corpo del metodo della funzione Insert
ed inseriamo il seguente codice:
function insert(item, user, request) {
if(item.Nome.length >0 || item.Cognome.length > 0){
request.execute();
}
else {
request.respond(statusCodes.BAD_REQUEST, 'I Campi Nome e Cognome sono obbligatori');
}
}
Il codice è piuttosto semplice: nell'if
controlliamo la lunghezza dei campi di interesse (utilizzando il metodo di get definito sull'oggetto item) e se il controllo fallisce inviamo un messaggio di errore all'applicazione iOS. Il metodo respond
(definito sull'oggetto request
) prende in ingresso due parametri: lo status code relativo alla risposta che vogliamo inviare ed un'eventuale errore testuale. Terminata la scrittura del codice clicchiamo sul bottone salva in basso.
Torniamo alla nostra applicazione iOS e nel dizionario che utilizziamo per effettuare l'inserimento associamo una stringa vuota alla chiave Nome e stampiamo sulla console la stringa d'errore che abbiamo associato all'oggetto request
dello script:
NSDictionary *item = @{
@"Nome":@"",
@"Cognome" : @"B",
@"Anno" : @1999};
[table insert:item completion:^(NSDictionary * insertedItem, NSError * error) {
if (error) {
NSLog(@"%@", error.localizedDescription);
} else {
NSLog(@"Item inserted, id: %@", [insertedItem objectForKey:@"id"]);
}
}];
Effettuando un Run del progetto e provando ad effettuare un inserimento all'interno della tabella verrà mostrato sulla console l'errore precedentemente definito nello script.
Ovviamente lo script d'esempio che abbiamo mostrato effettua una validazione dei campi molto elementare, ma nulla vieta di realizzare script Javascript molto più complessi per effettuare una validazione più approfondita dei valori di input (come per esempio validare la struttura di un indirizzo mail).