Spero che "ve ne arriviate" dalla lettura delle altre due parti precedenti della serie: in tal caso grazie dell'attenzione e della pazienza per gli inevitabili strafalcioni che avrete letto.

Giunto a questo punto procederò ad analizzare il codice di una prima versione della app: per inziare il backend e quindi quello della app Focac-Book, che è scritta in Xamarin.Forms.

Trovate il link per accedere al codice presentato in linkografia (la mia pagina github).

La prima versione di cui parlerò in questa puntata della serie, e anche nella prossima, non dispone ancora di tutte le caratteristiche cui facevo cenno all'inizio: in particolare NON dispone di offline-sync (cioè lavora solo in modalità connessa) e neanche dispone di alcun controllo circa l'autorizzazione e l'autenticazione.

Infatti vorrei procedere per gradi: ad ogni puntata presenterò un codice via via più complesso che disporrà di caratteristiche aggiuntive, in modo tale da permetterVi (o almeno spero) di cogliere tutte le sfumature dell'implementazione finale.

Iniziamo con l'analisi del codice del backend: la prima cosa da osservare è che si fa un uso pesante di entity framework. Qui, come nella app, i dati sono rappresentati esclusivamente tramite modelli, e l'interazione con essi è possibile solo con Focac_BookContext, che altro non è che l'implementazione del DBContext.

Come noto in entity framework è molto importante definire all'inizio se si usa l'approccio code-first o database-first.

Oss.: Si, lo so, esiste anche il model-first come approccio, ma questo proprio lo sopporto e non l'ho mai nemmeno capito appieno, per cui faccio finta che non esista...... cosa dicevate: model cosa ??

Con l'approccio code-first l'idea di sviluppo è che per prima cosa si definiscano le classi che rappresentano i dati: una volta terminata l'operazione entity framework si occuperà di creare le tabelle per noi con la struttura analoga a quanto definita nei modelli (con l'ausilio del processo detto di migration).

Se per caso si modifica un modello, cioè si aggiunge un campo in una classe, sempre entity framework agirà per aggiungere questo campo nella tabella, cioè inoltrerà al database le query necessarie per aggiornare le struttura della tabella coinvolta. L'effetti finale sarà che entity framework si occuperà di mantenere tipo e quantità delle proprietà denunciate nei modelli uguali ai campi nelle tabelle sul database.

Analoga cosa accade se aggiungiamo un nuovo modello: in tal caso la relativa tabella sarà creata autonomamente.

Con l'approoccio database-first, invece, si creano prima le tabelle sul database, e quindi le classi modello devono essere create manualmente, o con l'ausilio di strumenti ad-hoc, e in modo tale da replicare la struttura di dette tabelle.

Le modifiche che si fanno sulla struttura delle tabelle nel database devono essere riportate, sempre manualmente, sulle relative classi modello associate.

Detto in altri termini con l'approccio database-first è onere del programmatore tenere in sincronia le strutture delle tabelle sul database e delle classi modello, pena il verificarsi di errori bloccanti. Con il code-first, invece, fa tutto per noi entity framework.

Ora non so con onestà quale sia l'approccio migliore tra quelli esposti. Io dico solo che a me non piace molto che entity framework traffichi troppo con i miei dati e quelli dei miei clienti, per cui per mia abitudine uso quasi sempre l'approccio database-first.

E così ho fatto anche per il backend di Focac-Book: quindi quale padrone assoluto del codice, dell'analisi, nonchè gran sultano e visir del progetto affermo che la mia scelta è perfetta ed è la migliore possibile. Lunga vita, quindi, all'approccio database-first.

Quindi, per sancire quanto democraticamente deciso, all'interno della classe StartUp, metodo ConfigureMobileApp troverete la riga seguente, che informa entity framework che si usa l'approccio database-first.

.....
Database.SetInitializer(null);
......

Rammento che la classe StartUp è utilizzata per configurare le varie parti del backend ogni qualvolta il codice viene richiamato in memoria per la sua esecuzione (per cui ad ogni avvio o riavvio di IIS).

Ora diamo uno sguardo al modello del backend.

public class FocaccePost : EntityData
{
  public string NomeUtente { get; set; }
  public string Luogo { get; set; }
  public DateTime DataOra { get; set; }
  public int Voto { get; set; }
}

La struttura sopra indica i dati che dovranno essere trattati dalla nostra app.

NomeUtente Il nome del segnalatore del ,uogo ove ha gustato la focaccia
Luogo Descrizione del luogo dove si è proceduto all'assaggio
DataOra Quando si è proceduto all'assaggio
Voto Numero da 1 a 5 che indica quanto si è apprezzato l'assaggio della focaccia

 

La struttura dei dati è veramente minimale e semplicistica: per esempio in una app seria il luogo dovrebbe essere costituito da un dato che rappresenta una geolocalizzazione, ma qui ho voluto tenere il tutto molto semplice per evitare di perdersi in dettagli ininfluenti per l'esposizione.

GGuardando la classe modello del backend è impossibile non notare che il modello deriva da EntityData, che a sua volta implementa l'interfaccia ITableData.

public class FocaccePost : EntityData
public abstract class EntityData : ITableData
{
	protected EntityData();

	[System.ComponentModel.DataAnnotations.KeyAttribute]
	[TableColumn(TableColumnType.Id)]
	public string Id { get; set; }
	[TableColumn(TableColumnType.Version)]
	[System.ComponentModel.DataAnnotations.TimestampAttribute]
	public byte[] Version { get; set; }
	[System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute]
	[Index(IsClustered = true)]
	[TableColumn(TableColumnType.CreatedAt)]
	public DateTimeOffset? CreatedAt { get; set; }
	[System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute]
	[TableColumn(TableColumnType.UpdatedAt)]
	public DateTimeOffset? UpdatedAt { get; set; }
	[TableColumn(TableColumnType.Deleted)]
	public bool Deleted { get; set; }
}


public interface ITableData
{
	string Id { get; set; }
	byte[] Version { get; set; }
	DateTimeOffset? CreatedAt { get; set; }
	DateTimeOffset? UpdatedAt { get; set; }
	bool Deleted { get; set; }
}

I campi aggiunti hanno lo scopo di permettere l’utilizzo del Mobile Azure App Service: la struttura reale della tabella su Sql Server quindi deve essere la seguente.

CREATE TABLE [dbo].[FocaccePost](
	[Id] [nvarchar](128) NOT NULL,
	[Version] [timestamp] NOT NULL,
	[CreatedAt] [datetimeoffset](7) NOT NULL,
	[UpdatedAt] [datetimeoffset](7) NULL,
	[Deleted] [bit] NOT NULL,
	[NomeUtente] [varchar](50) NOT NULL,
	[Luogo] [varchar](50) NOT NULL,
	[DataOra] [datetime] NOT NULL,
	[Voto] [int] NOT NULL,
 CONSTRAINT [PK_FocaccePost] PRIMARY KEY NONCLUSTERED 
(
	[Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[FocaccePost] ADD  CONSTRAINT [DF_FocaccePost_Id]  DEFAULT (newid()) FOR [Id]
GO

ALTER TABLE [dbo].[FocaccePost] ADD  CONSTRAINT [DF_FocaccePost__CreatedAt]  DEFAULT (sysutcdatetime()) FOR [CreatedAt]
GO

ALTER TABLE [dbo].[FocaccePost] ADD  CONSTRAINT [DF_FocaccePost_UserId]  DEFAULT ('') FOR [NomeUtente]
GO

ALTER TABLE [dbo].[FocaccePost] ADD  CONSTRAINT [DF_FocaccePost_Luogo]  DEFAULT ('') FOR [Luogo]
GO

ALTER TABLE [dbo].[FocaccePost] ADD  CONSTRAINT [DF_FocaccePost_Voto]  DEFAULT ((0)) FOR [Voto]

Come si può apprezzare la chiave della tabella è il campo Id, che assumerà valori GUID: non è possibile fare altrimenti, e per preservare eventuali semantiche dei dati (chiavi che insistono su altri campi, per esempio) si può, invero facilmente, agire sfruttando le varie possibilità offerte da entity framwerk (vedi relationship, gestione delle foreign key, etc etc) e/o anche codice esterno sia questo posto nel backend che anche direttamente su Sql Server (vedi trigger o altri supporti).

Mettetevi il cuore in pace: i Mobile App Service prevedono che la chiave sia chiamata Id e che il valore sia una Guid. Piagnistei e critiche non servono. Inoltre siete sicuri che per supportare una buona concorrenza nonchè offrire buone perfomance si possa usare un counter (identity) fornito da Sql Server come si faceva negli anni in cui Pertini era presidente della repubblica ? O che i Duran Duran cantavano Wild Boys ?

Il campo CreatedAt, che dispone anche di un valore di default, è utilizzato internamente dall’infrastruttua Azure per ottimizzare l’accesso ai dati, e deve essere valorizzato con la data di creazione della tupla.

In definitiva serve come chiave nel partizionamento orizzontale della tabella sulle risorse fisiche: viene tutto gestito da Azure, ma è compito del programmatore assegnarli un valore sensato per avere le massime perfomance dell’utilizzo del sistema. Nella pratica ci si leva dall'impiccio con un valore di default come esposto sopra.

Lo scopo degli altri campi sarà chiaro nel seguito: per ora basti dire che permettono di gestire la concorrenza delle modifiche concorrenti nonché la cancellazione degli elementi.

Ora alcune note sul DBContext di Entity Framework.

public class Focac_BookContext : DbContext
{

    private const string connectionStringName = "Name=MS_TableConnectionString";

    public Focac_BookContext() : base(connectionStringName)
    {
    } 

    public DbSet Focacce { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString())); //elimina la convenzione EF per le tabelle che devono avere il nome in plurale modelBuilder.Conventions.Remove(); } }

Per iniziare notate la stringa di connessione: Vi ricordate che nel portale Azure si è definita una stringa con uguale indicatore (in Application Settings) ?? Il senso è che quando si esegue il deploy del codice sul backend la stringa di connessione viene sostituita con quella definita nel portale Azure in modo autonomo e senza dover fare nulla.

Altra cosa interessante è la riga Conventions.Remove. Infatti in entity framework il nome delle tabelle devono essere al plurale: a me questa convenzione non piace per cui l'ho levata con questa riga.

Il cuore del backend comunque è il controller: siccome al momento Vi è solo una tabella allora sarà presente un solo controller. Come è possibile vedere sono implementati 5 metodi che permettono di coprire tutte le esigenze di accesso al database.

Nome MetodoHTTP VerbScopo
GetAllFocaccePost() Get Tutti i post
GetFocaccePost(string id) Get Il solo post con Id passato come argomento
PatchFocaccePost(string id, Delta patch) Patch

Modifica di un post.

Id = Identificativo post da modiifcare

patch = Valori da modificare

PostFocaccePost(FocaccePost item) Post Inserimento nuovo post
DeleteFocaccePost(string id) Delete Cancellazione di un post

 

Ora non rimane altro che compilare il backend e porlo in funzione in Azure: per fare questa operazione esistono decine di modi, ma quello che preferisco è il seguente.

Cliccare sul progetto e quindi azionare la scelta Publish: da questa sceta sarà possibile impostare i parametri di accesso allo spazio di App Mobile Service, cosa che si può fare semplicemente importando il file Get Publish Profile che è stato prelevato come da post predente della serie.

Una volta importato detto file (al cui interno sono presenti tutti i parametri di connessioni, comprese password etc etc) basta solo confermare et voilà il nostro codice viene compilato e deployato su Azure: siamo quindi pronti per passare sfruttare il fantastico codice con la App (parte client).

Linkografia

Git Page: Focac-Book in Xamarin