WebApi
Un approccio alternativo ai Web service basati su SOAP, che è stato proposto negli stessi anni ma è stato riscoperto soltanto negli ultimi tempi, è l’approccio noto come REST (Representational State Transfer) e basato su una serie di principi che un’applicazione deve rispettare. In sintesi, nella visione RESTful un Web service non definisce una funzione richiamabile da remoto ma mette a disposizone delle risorse su cui è possibile effettuare le classiche operazioni CRUD sfruttando i metodi del protocollo HTTP. Si parla di Web service quando si fa riferimento a quelli basati su SOAP e di Web API quando ci ispira al modello REST. Sfruttare WCF per realizzare Web service secondo l’approccio RESTful non è cosa immediata. Con ASP.NET Web API, Microsoft ha deciso di cambiare approccio nella realizzazione di Web service RESTful basandoli sul meccanismo di routing di MVC invece di cercare di semplificare la complessità intrinseca del sottosistema WCF.
Componenti
Una Web API è costituita essenzialmente da risorse accessibili via Web tramite una o più rappresentazioni. Una risorsa è generalmente un’istanza di una classe che viene inviata al client dopo averla serializzata. ASP.NET Web API supporta in maniera predefinita la serializzazione in JSON e XML. Ciascuna risorsa è rintracciabile sul Web tramite uno specifico URI, mappato nel nostro framework sfruttando il meccanismo di routing di ASP.NET. La gestione dei metodi HTTP applicabili alle risorse è affidata ai Controller, un tipo di classe i cui metodi implementano le richieste giunte all’applicazione via HTTP. In sostanza, un client richiede l’esecuzione di un metodo HTTP, ad esempio GET, su una risorsa identificata da un URI. Il framework, interpretando l’URI, individua il controller associato alla risorsa e, in base allo specifico metodo HTTP ed ai parametri specificati dal client, invoca il metodo corrispondente. Il risultato dell’esecuzione del metodo è una risorsa e/o un codice di stato HTTP. Se viene restituita una risorsa, questa viene serializzata in base al formato richiesto dal client tramite l’intestazione HTTP Accept.
Convenzioni
Lo schema dell’URI
La prima convenzione è quella che riguarda lo schema dell’URI per individuare una risorsa. La route definisce uno schema di URL con {controller} e {id} come segnaposto per il controller della risorsa e l’eventuale identificatore.
/api/{controller}/{id}
Alla ricezione di una richiesta HTTP, il sistema analizzando l’URL andrà alla ricerca di un controller per la risorsa specificata. L’individuazione del controller viene effettuata concatenando al nome indicato al posto di {controller} con la parola chiave controller. Quindi, facendo riferimento al nostro esempio, per gestire l’URL /api/items/API, il sistema cercherà un controller con nome ItemsController. Per capire quale metodo del controller invocare, il sistema fa riferimento al metodo HTTP utilizzato dal client. In pratica, se il client ha utilizzato il metodo GET, il sistema andrà alla ricerca di un metodo del controller il cui nome inizia proprio con get, individuando nel nostro caso il metodo GetItemByTerm(). Lo stesso meccanismo sarà valido per i metodi PUT, POST e DELETE.
WCF 4.0
Introduzione
WCF è l’acronimo di Windows Communication Foundation e rappresenta una delle ultime tecnologie sviluppate da Microsoft per lo sviluppo di applicazioni in ambiente distribuito e per la comunicazione tra applicazioni. Rappresenta un modello di programmazione unificata per la costruzione di applicazioni orientate ai servizi. WCF è una tecnologia che mette insieme ASMX Web Services, WSE, .Net Remoting, System Messaging ed Enterprise Service.
ABC
L’acronimo ABC (Address Binding Contract) è la chiave di lettura per capire la composizione di un servizio WCF e del suo funzionamento.
Address - Dove si trova il servizio?
Indica la locazione specifica di un servizio, il “posto” dove inviare i messaggi. Tutti i servizi WCF sono distribuiti da uno specifico indirizzo rimanendo in ascolto di eventuali richieste. Un indirizzo è normalmente specificato da un URL, dove la prima parte specifica il meccanismo di trasporto mentre la parte restante specifica la locazione univoca del servizio.
Ad esempio l’indirizzo http://www.wcfservices.com/myservices/myfirstservice è un address WCF che utilizza HTTP come protocollo di trasporto, il servizio è presente sul server www.wcfservices.com raggiungibile tramite il percorso univoco myservices/myfirtservice.
Binding - Come parlo con il servizio?
Le associazioni (bindings) sono utilizzate per specificare il dettagli relativi al transport, all’encoding, e al protocol richiesti dal client per poter comunicare con il servizio. Un binding è costituito da una collezione di elementi dove ogni elemento specifica un aspetto di come il servizio comunica con i client. Un binding deve includere almeno un elemento relativo al transport (ad esempio TCP o HTTP), uno all’encoding (ad esempio Text o Binary) dei messaggi ed uno al protocol. Alcuni dei Bindings forniti “gratis” da WCF sono:
BasicHTTPBinding,WSHTTPBinding, WSDualHttpBinding,WSFederationHttpBinding, NetTcpBinding, NetNamedPipeBinding, NetMsmqBinding, NetPeerTcpBinding e MsqmIntegrationBinding.
Contract - Che cosa può fare il servizio per me?
Un contratto WCF è un insieme di specifiche che definisce l’interfacce di un servizio WCF. Un servizio WCF comunica con altre applicazioni accordandosi tramite “contratti”. Ci sono diversi tipi di contratto: Service Contract, Operation Contract, Data Contract, Message Contract e Fault Contract.
- Service Contract:informa che cosa il servizio può fare. Un servizio WCF ha almeno un Service Contract.
- Operation Contract: definito all’interno di un Service Contract, definisce i parametri e i tipi di ritorno di un’operazione. Un’operazione può prendere in ingresso dati primitivi come ad esempio interi, tipi complessi definiti tramite Data Contract o tipi definiti tramite Message Contract. Un Operation Contract definisce anche altre impostazioni come la direzione (ad esempio one-way o two-way) o il Fault Contract, uno o più errori che possono presentarsi durante l’esecuzione dell’operazione.
- Message Contracts: Se un’operazione ha necessità di passare un messaggio come parametro o ritornare un messaggio, il tipo di questi messaggi è definito dai Message Contracts. Questo tipo di messaggio definisce alcuni aspetti come quelli legati alla sicurezza o se un determinato elemento deve essere inserito all’interno del body o dell’header del messaggio.
- Data Contracts: specificano i tipi di dato del servizio WCF. Tutti i tipi di dato usati dal servizio WCF devono essere descritti dai metadati (tramite il Data Contract) affinché le applicazioni possano interagire con il nostro servizio. Un Data Contract può essere utilizzato da un Operation Contract come parametro o return type, oppure può essere utilizzato da un Message Contract per definire elementi del messaggio.
- Fault Contract: specifica un’eventuale errore che può essere ritornato al chiamante del servizio. Ogni operazione di un servizio può avere zero o più Fault Contracts.
Endpoint
Un Endpoint è un “posto” dove le comunicazioni sono inviate e/o ricevute (secondo della configurazione). La comunicazione avviene tra Endpoints. Un servizio può esporre uno o più Application Endpoints (zero o più Inftrastructure Endpoints). Un servizio espone queste informazioni come metadati che le applicazioni processano per generare gli opportuni WCF client da utilizzare per la comunicazione con il servizio. Quando necessario un client genera un Endpoint compatibile con uno degli Endpoints esposti dal servizio. Ogni Endpoint è descritto ad un indirizzo, un binding ed un Service Contract (WCF ABC).
Hosting
Un servizio WCF è un componente che può essere chiamato da altre applicazioni. Deve essere eseguito in un ambiente per poter essere “scoperto” (discovered) e usato. L’host WCF è un’applicazione che controlla la vita del servizio. Come vedremo ci sono diversi modi per eseguire l’hosting di un servizio WCF.
Self Hosting
Un servizio WCF può essere self-hosted, ovvero il servizio può girare come applicazione standalone e auto controllare il proprio lifetime. Questo è il modo più flessibile e semplice di eseguire l’hosting di un servizio WCF, avendo però funzionalità limitate.
Windows Services Hosting
Un servizio WCF può anche essere ospitato (hosted) come servizio Windows (un processo gestito dal sistema operativo e automaticamente fatto partire con l’avvio di esso, se opportunamente configurato).
IIS Hosting
Uno dei migliori modi di eseguire l’hosting di un servizio WCF è utilizzare IIS. Per sua natura IIS ha molti funzionalità utili come process recycling, idle shutdown, process health monitoring, attivazione message-based, high availability, easy manageability, versioning e scenari di deployment, tutte funzionalità richieste da servizi WCF di livello enterprise.
Metadata
I metadata di un servizio descrivono le caratteristiche del servizio che un’entità esterna deve conoscere per poter comunicare con il servizio stesso. I metadata possono essere consumati tramite il ServiceModel Metadata Utility (scvutil.exe) per generare un client WCF e per fornire la configurazione che un’applicazione client deve avere per poter interagire con il servizio. I metadata esposti dal servizio includono XML schema che ne definiscono i Data Contract, documenti WDSL che ne descrivono i metodi. E’ possibile nascondere i metadata anche se non è una pratica comune.
WCF SelfCare.Next
Trilance.Selfcare.Next.DAL
Contiene le chiamate effettive al DB con gli script “provider”, che eseguono effettivamente la query (o la chiamata alla stored). Per esempio TicketProvider ha il metodo getNature() seguente
Trilance.Selfcare.Next.WCF
Wrapper per il DAL, creando dei Service. In particolare per ogni servizio hoo la creazione di una interface e del servizio effettivo figlio dell’interfaccia:
Trilance.Selfcare.Next.Web
Comunicazione del server con l’esterno, conoscono solo i ServiceReference. Quando modifico un serviceReference devo aggiornare la firma (tasto destro, aggiorna riferimento al serivizio) in modo che i suoi metodi possano essere visibili anche da quì.
Trilance.Selfcare.Next.Entities
Classi che rappresentano le tabelle del DB, i loro attributi sono le colonne delle tabelle con getter e setter. Non hanno metodi.
Trilance.Selfcare.Next.Settings
Soliti settings che valgono per tutta la soluzione (con i transform.zconfig)
Trilance.Selfcare.Next.Resources
I generics consentono di personalizzare un metodo, una classe o una struttura in base ai dati precisi su cui interviene.
Ad esempio, invece di usare la classe Hashtable
, che consente di avere chiavi e valori di ogni tipo, è possibile usare la classe generica Dictionary<TKey, TValue>
e specificare il tipo concesso per la chiave e quello concesso per il valore. Tra i vantaggi dei generics ci sono una maggiore riutilizzabilità del codice e l’indipendenza dai tipi.
Definizione
I generics sono classi, strutture, interfacce e metodi dotati di segnaposto (parametri di tipo) per uno o più dei tipi archiviati o usati. Una classe di raccolte generiche può usare un parametro di tipo come segnaposto per il tipo di oggetti in essa contenuti. I parametri di tipo vengono visualizzati come i tipi dei relativi campi e i tipi di parametri dei relativi metodi. Un metodo generico potrebbe usare il parametro di tipo come il tipo di valore restituito o come il tipo di uno dei parametri formali.
Classi
Nel codice seguente viene illustrata una definizione di classe generica semplice.
Quando si crea un’istanza di una classe generica, è possibile specificare i tipi effettivi da sostituire per i parametri di tipo. Ciò consente di stabilire una nuova classe generica, definita come una classe generica costruita, con tipi prescelti sostituiti a ogni occorrenza dei parametri di tipo. Il risultato è una classe indipendente dai tipi personalizzata in base alla scelta di tipi, come illustrato nel codice seguente.
public static void Main()
{
Generic<string> g = new Generic<string>();
g.Field = "A string";
}
Metodi
Una definizione di metodo generico è un metodo con due elenchi di parametri: un elenco di parametri di tipo generico e un elenco di parametri formali. I parametri di tipo possono apparire come tipo restituito o come tipi dei parametri formali, come illustrato nel codice seguente.
Assumiamo di avere la seguente classe, che possiede 3 attributi (pubblici) con un costruttore che permette di inizializzarne (secondo il vecchio metodo) solo due.
Questa classe può essere istanziata in questi modi
Routing
In ASP.NET WebApi è usato un routing basato su convenzioni, analogo a quello di Rails, le route vengono definite come attributi quindi sopra una classe e sono principalmente stringhe parametriche. Quando il framework riceve una richiesta, fa un match dell’URI con il template. Route di esempio:
In questo esempio, “customers” e “orders” sono stringhe, mentre “customerId” è una variabile, i seguenti URI matchano questo template:
- http://localhost/customers/1/orders
- http://localhost/customers/bob/orders
- http://localhost/customers/1234-5678/orders
RoutePrefix
Spesso tutte le route di un controller iniziano con lo stesso prefisso, per esempio:
In questo caso posso settare un prefisso comune per tutto il controller usando l’attributo RoutePrefix, trasformando così la route nel seguente modo:
Il RoutePrefix può anche includere parametri:
Vincoli di route
I vincoli di route permettono di scegliere un metodo del controller in base al tipo di parametro in ingresso, per esempio
La prima route verrà scelta solo per l’ID è un integer, altrimenti verrà scelta la seconda route. Posso anche includere più vincoli nella stessa route separati dai :, per esempio per chiamare una route solo se il parametro in ingresso è un numero intero con valore minimo 1 scrivo:
Parametri opzionali e valori di default
Se aggiungo un punto di domanda al parametro della route, questo diventa opzionale, se un parametro di route è opzionale devo definire un valore di default nel caso in cui questo non sia presente
In questo caso /api/books/locale/1033
e /api/books/locale
forniscono la stessa risorsa
Creare un progetto di test
Per prima cosa devo creare un nuovo progetto UnitTest, quindi
File -> Aggiungi -> Nuovo Progetto -> Progetto Unit Test -> Scrivere il nome (es. BankTests)
Devo ora prendere il riferimento alla soluzione Bank, quindi fare
Riferimenti -> aggiungi riferimento -> espandere soluzione -> selezionare Bank
Aggiungere le configurazioni
Dato che il progetto UnitTest è un progetto console (autolanciante), non può appoggiarsi a delle configurazioni di un oggetto padre (come per le WebApi che si appoggiano a ApiNext), è necessario quindi aggiungere i file di configurazione analoghi a ApiNext. Per prima cosa installare ConfigZilla
Tasto dx -> gestisci pacchetti NuGet -> sezione Trilance.NuGet -> ConfigZilla.Setty
Aggiungere quindi il file di configurazione
Aggiungi -> nuovo elemento -> file di configurazione dell'applicazione
Chiamandolo App.ztransform.config. Ora inserirvi le configurazioni richieste (per le connectionString scrivere:
Creare un nuovo file di configurazione chiamandolo App.config e ** escluderlo dal codice sorgente (file → controllo del codice sorgeìnte → escludi App.config dal controllo del codice sorgente). Ora, compilando la soluzione, tale file dovrebbe essere autocompletato dal plugin Configzilla automaticamente.
Aggiungere la classe di Test
In Esplora soluzioni selezionare il file UnitTest1.cs nel progetto BankTests, rinominarlo e aggiungere il riferimento alla classe testata
Scrivere il test
Il metodo da controllare è il seguente:
Un primo test potrebbe essere il seguente
Compilare la soluzione
Per aprire la finestra di esplorazione test fare
Test -> Finestre -> Esplora Test
Da qui posso eseguire tutto o solo i test falliti e così via. Eseguo questa procedura e il test viene superato correttamente.
Usare il metodo ExpecteDEXception
Dato che il metodo da testare genera delle eccezzioni posso usare il metodo Expecte[[DEX]]ception
nel seguente modo
Configurare una soluzione con ConfigZilla
Nella soluzione principale, sotto Enviroment c’è un file chiamato ConfigZilla dove c’è scritto il path dove andare a prendere le configurazioni, per esempio
Trilance.ApiNext.Settings\
oppure:
Trilance.ApiNext.Settings\trilance\amga\amga-test
in tale cartella troviamo delle cartelle e un file chiamato transform.zconfig
che sarà il file con tutte le configurazioni (JSON) da usare nel progetto, per esempio le connection string:
Se ho un path profondo, tipo
Trilance.ApiNext.Settings\trilance\amga\amga-test
lui prenderà il zconfig del path in questione facendo un merge con i zconfig dei padri (in caso di conflitto prenderà il valore del figlio).
Configurare un ambiente di test
Nel file configZilla indicare la stringa Trilance.Selfcare.Next.Settings\FO\Trilance
, che indica che il database utilizzato sarà magneto. Notare che configZilla è un file che non viene caricato in VSS, ognuno quindi può mettere la configurazione che vuole senza influenzare gli altri.
Importazione DLL
Per importare una DLL il modo migliore è creare un nuovo pacchetto NuGet con NuGet Package Explorer. open local package → \connor\tools\Trilance.NuGet\nome.lib Una volta fatto lo pubblico nel path corretto (\connor\tools\Trilance.NuGet) e quindi questo è accessibile a tutti, per importarlo clicco sul destro nella soluzione che voglio (GestioneRate per esempio), gestione pacchetti nuget, trilance.NuGet, e lo trovo, installo/aggiorno e tutto funziona correttamente.
Importare configurazioni di una DLL esterna con App.config
Assumo di voler utilizzare una DLL esterno il cui codice sorgente è (in VSS) nel seguente path
$/SWDEV/2012/Trilance.ApiNext.root/Trilance.ApiNext
In tale path, oltre al codice sorgente, troviamo anche un file App.config dove sono indicate le configurazioni necessarie affinchè questa possa funzionare. In particolare è indicata la seguente connection string (richiesta dal pacchetto DLL in questione):
Affinchè la DLL possa funzionare, tale ConnectionString deve essere inserita all’interno del progetto principale, per fare ciò utilizziamo il path
Trilance.ApiNext/Web.ztransform.config
dove sono indicate le connectionStrings da utilizzare nella soluzione, per esempio da questa situazione:
passo a
Notare che le connectionString presenti hanno un placeholder (per esempio centroNotifiche.connectionString
) che fa riferimento a quello dei settings, conseguentemente in transform.zconfig
troverò:
Fatto questo la configurazione è stata eseguita correttamente e la DLL potrà funzionare correttamente.
Aggiunta di riferimenti
Utilizzare metodi in altri progetti della stessa soluzione
Esempio classico sono le WebApi che vogliono usare i metodi della DLL con la logica. Tasto dx sulla cartella riferimenti → aggiungi riferimento → Spunta sul progetto da integrare
Rendere visibile una WebApi
Quando chiamo una WebApi lui fa riferimento sempre alla WebApi principale (per esempio Trilance.ApiNext). Se ho creato una nuova WebApi e voglio renderla visibile tasto destro sulla cartella riferimenti di Trilance.ApiNext → aggiungi riferimento → spuntare la WebApi in questione. Nel caso si utilizzi il ConfigurationManager che non viene visto ricorda di aggiungere il riferimento col metodo sopra (sotto la cartella Assembly e utilizza la ricerca)
Risolvere problemi di configurazioni differenti (newtonsoft 4.5)
Nel caso in cui non si riesca a risolvere l’errore HRESULT: 0x80131040 copincollare il seguente codice in App.config. Questo sovrascrive in runtime le configurazioni in modo che il compilatore sia ok.
Parametri in ingresso al controller
Quando un metodo di controller è in post, significa che ha come primo parametro in ingresso un oggetto complesso in POST, gli altri parametri invece sono in query string. Nel caso in cui io abbia due oggetti da passare avrò un oggetto complesso che wrappa i due oggetti complessi in questione. Se per esempio ho due metodi POST che hanno come primo parametro lo stesso oggetto complesso devo differenziarli (se così non fosse la differenziazione sarebbe automatica) cambiando la route.
Metodi POST
Nel Body, in raw inserisco il JSON con un l’oggetto complesso che voglio provare. Esempio:
Mappare un oggetto fornito dal DB in un altro
Quando ho delle Stored che restituiscono un oggetto ma le mie specifiche richiedono che l’oggetto che fornisco al frontend sia una rielaborazione di questo, posso fare due classi: una classe entity privata (TicketConfigurationDB) che mappa esattamente ciò che è stato fornito dal server e una classe pubblica (*TicketConfiguration *) che sarà la classe fornita dal controller. La classe privata avrà un metodo (ToTicketConfiguration()) che trasformerà tale oggetto nell’oggetto *TicketConfiguration * richiesto dal frontend.
Entity
Chiamata alla Stored e conversione
Quando devo lavorare con degli id costanti, che però ovviamente dipendono dal database con cui sto lavorando, devo creare delle config a livello di WebApi con il seguente metodo
- Verificare a che config punta il
configZilla
e verificare che sia corretto - Aggiungere la costante nel
transform.zconfig
associato - Recuperare il valore della costante con il seguente metodo
Nel caso in cui vi sia la necessità di eseguire degli update o degli insert su DB conviene eseguirli all’interno di una transazione, nel caso in cui ci siano problemi SQL è possibile eseguire un rollback (