Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
I Web service
Come si detto nel Capitolo 1, un Web service una componente .NET che risponde a richieste
HTTP formattate secondo la sintassi SOAP. I Web service rappresentano una delle chiavi di volta
delliniziativa .NET, nel senso che, grazie a Internet, consentono un grado di interoperabilit
tra le applicazioni assolutamente inconcepibile no a poco tempo fa.
Data limportanza che i Web service rivestono, qualcuno potrebbe sorprendersi nel vedere
il relativo poco spazio che stato loro dedicato in questo libro.
La spiegazione, in realt, semplice: i Web service sfruttano molte funzionalit del .NET
Framework ampiamente descritte nei precedenti capitoli. Innanzitutto, dal momento che i Web
service non sono altro che applicazioni ASP.NET o pi precisamente, vengono implementati
come gestori HTTP che intercettano richieste da le .asmx possibile applicare la maggior
parte dei concetti descritti nel Capitolo 26 e nel Capitolo27, tra cui la gestione dello stato, la
memorizzazione nella cache delloutput e lautenticazione.
Come si potr vedere, sar possibile creare Web service ancora migliori mettendo in pra-
tica le nozioni sulla serializzazione XML del capitolo 23 e quelle sulle operazioni asincrone
del Capitolo 12.
Nota Per mantenere il codice il pi compatto possibile, tutti gli esempi di questo capitolo assumono
linserimento delle seguenti istruzioni Imports allinizio dei le sorgente:
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Net
Imports System.Threading
Imports System.Web.Security
Imports System.IO
Imports System.XML.Serialization
Imports System.Text.RegularExpressions
1048 Parte VI Applicazioni Internet
Il le Service1.asmx contiene il codice del Web service il quale ancora una volta rappresenta
una componente .NET alla quale si accede da Internet via HTTP. Prima di proseguire con lesempio,
sarebbe bene attribuire a questo le un nome pi descrittivo, come Converter.asmx.
Come i le delle Web Form, anche i le .asmx sono dotati di unarea di progettazione sulla
quale possibile trascinare le componenti .NET. Dal momento che i Web Service non possiedono
interfaccia utente, tale caratteristica verr utilizzata solo per le componenti non visuali, come le
connessioni di ADO.NET o gli oggetti FileSystemWatcher. Il passo successivo consiste nel premere
F7 e passare alleditor di codice: come si pu vedere, Visual Studio ha gi creato una classe Web
Capitolo 29 I Web service 1049
service funzionante denominata Service1, con un metodo Hello World desempio pronto per es-
sere decommentato. A questo punto si rinomina la classe Converter per mantenerla conforme al
nome del le e si aggiungono i due metodi per convertire le valute, inserendoli dopo la procedura
desempio commentata. Quello che segue il codice del primo Web service. Per risparmiare spazio,
ho omesso il codice generato automaticamente nel blocco #Region:
<WebService(Description:=A web service for converting currencies,
Namespace:=http://tempuri.org/MoneyConverter/Service1) > _
Public Class Converter
Inherits System.Web.Services.WebService
Il codice effettivo per i due metodi talmente semplice che non vale la pena commentarlo.
Piuttosto, bene soffermarsi sugli attributi utilizzati nel listato precedente:
n Lattributo WebService qualica la classe Converter come una classe Web service. Questo
attributo non realmente indispensabile in quanto lestensione .asmx e il fatto che la classe
Converter derivi da System.Web.Services.WebService sono sufcienti a comunicare allinfra-
struttura di ASP.NET lintenzione di realizzare un Web Service.Tuttavia, lattributo WebService
utile per impostare propriet aggiuntive del Web service, come la relativa descrizione e il
namespace, e di regola non andrebbe mai omesso.
n Lattributo WebMethod rende un metodo della classe accessibile tramite Internet. Solo i metodi
contrassegnati con tale attributo risultano visibili da client remoti, pertanto non consentito
ometterli. possibile utilizzare tale attributo per associare una descrizione al metodo e per
denire altre propriet importanti, come il tipo di gestione per lo stato della sessione e il ca-
ching. (Per ulteriori informazioni a riguardo, si rimanda al paragrafo Lattributo WebMethod,
pi avanti in questo capitolo).
lancer, quindi, una nuova istanza di Microsoft Internet Explorer e far in modo che punti alla
pagina .asmx. La Figura 29-2 mostra quanto viene visualizzato nel browser. Il titolo della pagina
il nome della classe, e la propriet description dellattributo WebService appare immediatamente
al di sotto, seguita dallelenco dei metodi disponibili e dalle rispettive descrizioni.
Figura 29-2 La pagina HTML che ASP.NET crea al volo se si accede a un le .asmx senza passare
alcun parametro
Ma da dove provengono queste informazioni? Quando ASP.NET intercetta una richiesta per
una pagina .asmx senza alcun parametro nella query string, utilizza la reection per estrarre i
nomi degli attributi e dei metodi della prima classe del le .asmx, e quindi sintetizza la pagina
HTML. (Se il le contiene pi classi che derivano da System.WebServices.WebService, solo la
prima classe sar visibile nel browser).
In unapplicazione reale, i client accedono al servizio da codice, ma questa utile funzionalit
consente di collaudare il servizio in modo interattivo durante la fase di debugging. La pagina
che genera loutput in questione non altro che una pagina .aspx. Pi precisamente, il le Defa
ultWsdlHelpGenerator.aspx memorizzato nella directory C:\Windows\Microsoft.NET\Framework\
vx.y.zzzz\Cong. Dal momento che si tratta di una pagina .aspx standard, possibile persona-
lizzarla a seconda delle necessit, aggiungendo, ad esempio, il logo della societ o modicando
la pagina per nascondere informazioni sul Web service. anche possibile modicare alcune
costanti denite in prossimit dellinizio della pagina per abilitare la modalit debugging o altre
funzionalit
Il fatto di apportare modiche al le DefaultWsdlHelpGenerator.aspx inuisce su tutti i Web
Service in esecuzione sul computer. Per un controllo pi ne sulle modalit di utilizzo della
pagina, possibile aggiungere un tag <wsdlHelpGenerator> al le web.cong nella directory
radice dellapplicazione oppure in quella che contiene il le .asmx:
<configuration>
<system.web>
<webServices>
<wsdlHelpGenerator href=LocalWsdlHelpGenerator.aspx />
</webServices>
</system.web>
</configuration>
Capitolo 29 I Web service 1051
La pagina di help standard offre ben pi di una semplice descrizione del Web service e dei
relativi metodi: consente, infatti, di invocare in modo interattivo i singoli metodi passando loro tutti
gli attributi necessari. Non tutti i metodi possono essere collaudati in questo modo ad esempio
non quelli che accettano oggetti o argomenti ByRef ma sicuramente un enorme vantaggio
quando si collauda il servizio.
La Figura 29-3 mostra la pagina che appare quando si seleziona il nome di un metodo (Dollar-
ToEuro, in questo esempio). Basta inserire un valore qualsiasi nella casella Amount e premere il
pulsante Invoke. Il valore di ritorno del metodo verr visualizzato sotto forma di XML allinterno
di una nuova istanza di Internet Explorer (Figura 29-4). LURL utilizzato per ottenere il risultato
ha il seguente formato:
http://servername/webservicename/pagename.asmx/methodname
Un client locale che sa come ricercare questo Web service e quali argomenti passare pu in-
vocare un metodo inviando una richiesta HTTP POST.
1052 Parte VI Applicazioni Internet
Come sar possibile apprendere in seguito, questo solo uno dei tre protocolli utilizzabili
per interrogare un Web Service da Internet, gli altri due sono HTTP GET e SOAP.
La pagina di help principale del Web service contiene un link che pu rivelarsi interes-
sante. Se si seleziona il collegamento Service Description, il browser visualizza il contratto
Web Service Description Language (WSDL).
Si tratta di un le che descrive il Web service appena creato, con informazioni su ciascun
metodo e sugli argomenti che questo si aspetta. possibile esaminare il contratto WSDL di
una pagina .asmx direttamente dal browser aggiungendo largomento ?WSDL allURL della
pagina .asmx.
Sebbene i Web Service si basino pesantemente sui le WSDL, nella maggior parte dei casi
non sar necessario preoccuparsi n di questi n delle informazioni che contengono in quanto
Visual Studio sar in grado di gestire questi le in modo trasparente.
Nota Per approfondire ulteriormente il contratto WSDL e loperazione di ricerca di quali Web service
siano disponibili su un sito Web, si leggano gli argomenti Web Service Description Language Tool
(Wsdl.exe) e Web Service Discovery Tool (Disco.exe) nella documentazione MSDN.
1. Si aggiunge un progetto Windows Forms alla soluzione corrente, gli si attribuisce il nome
MoneyConverterClient, e lo si rende il progetto di start-up per la soluzione.
2. Si seleziona il comando Add Web Reference dal menu Project o dal menu di contesto che
appare effettuando un clic con il tasto destro del mouse sul nodo del progetto nella nestra
Solution Explorer.
3. La nestra di dialogo che appare consente di selezionare un Web Service tra quelli registrati
localmente o in una directory Universal Description, Discovery, and Integration (UDDI) me-
morizzata nella rete locale, gestita da Microsoft o da altri (Figura 29-5). Le directory UDDI
rappresentano una sorta di pagine gialle per i Web Service nel senso che elencano i servizi
distribuiti in Internet. (Per ulteriori informazioni sugli UDDI possibile visitare il sito http:
//www.uddi.org).
Capitolo 29 I Web service 1053
4. Si seleziona il link W
eb Services On The Local Machinee il Web service Converter dallelenco
che appare, o basta immettere il percorso del le .asmx sulla macchina locale ossia, http:
//localhost/MoneyConverter/converter.asmx nel campo URL e si preme Invio. Il pannello
di sinistra mostrer la pagina di descrizione associata al Web Service (Figura 29-6).
Figura 29-6 Inserimento dellindirizzo di una pagina .asmx che abilita il pulsante Add Reference
1054 Parte VI Applicazioni Internet
5. Si pu utilizzare il pannello di sinistra per collaudare il Web service ma, dal momento che il Web
service funziona correttamente, possibile procedere e premere il pulsante Add Reference. (In
questo primo esperimento, si accetti il nome di default localhost del riferimento Web, anche
se di solito in un progetto reale si modicher questo nome con uno pi descrittivo).
Visual Studio aggiunge una nuova cartella Web References al progetto, con un nodo localhost
che raccoglie tutti i le che ha creato o che ha scaricato dal Web service: ad esempio, il le del
contratto WSDL. Il le principale, dal punto di vista dellapplicazione client, Reference.vb. (Per
visualizzarlo necessario premere il pulsante Show All Files allinterno del Solution Explorer
ed espandere il nodo Web References, Localhost e Reference.map). Questo le contiene una
classe Converter che espone gli stessi metodi dei Web Service originali (e qualcuno in pi) e
che funge da proxy tra lapplicazione Windows Form e il Web service in esecuzione da qualche
parte su Internet (Figura 29-7).
Invece di inviare le richieste HTTP al Web service, lapplicazione client invoca i metodi di
questa classe proxy, e questa, a sua volta, reindirizza la chiamata al Web service utilizzando il
protocollo HTTP.
Figura 29-7 Come appare un Web reference nel Solution Explorer e nellObject Browser
Come spesso accade nel mondo .NET, questa piccola magia possibile grazie allereditariet. La
classe proxy Converter eredita molti dei propri metodi dalla classe di base System.Web.Services.
Protocol.SoapHttp ClientProtocol. Il pi importante di tali metodi Invoke, il quale effettua una
sorta di chiamata in late binding alla classe Web service remota. La classe generata automaticamente
da Visual Studio contiene procedure wrapper che consentono allapplicazione client di invocare
metodi remoti utilizzando una sintassi fortemente tipizzata:
...(Allinterno della classe Converter nel file Reference.vb)...
Capitolo 29 I Web service 1055
Una volta compreso il funzionamento di tale meccanismo, per invocare il Web service basta
creare unistanza della classe proxy e invocare uno dei relativi metodi, senza doversi preoccupare
di ci che accade dietro le quinte.
La Figura 29-8 mostra una semplice form che consente di convertire i dollari in euro e viceversa.
Quello che segue il codice che gestisce i due pulsanti:
Private Sub btnToEuros_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles btnToEuros.Click
Dim conv As New localhost.Converter() Crea il proxy.
Dim value As Decimal = CDec(txtDollars.Text) Ottiene largomento.
Dim result As Decimal = conv.DollarToEuro(value) Invoca il metodo remoto.
txtEuros.Text = result.ToString Visualizza il risultato.
End Sub
Nota Durante la fase di collaudo, capiter spesso di modicare la struttura del Web service ad
esempio, aggiungendo nuovi metodi o modicando la rma di quelli esistenti. Dopo ogni cam-
biamento allinterfaccia della classe, bisognerebbe ricreare la classe proxy nel progetto client. A
questo proposito, il modo pi semplice consiste nellutilizzare il comando Update Web Reference
del menu di contesto di ciascun riferimento (localhost, se non stato rinominato) contenuto nella
cartella Web References della nestra Solution Explorer.
<configuration>
<system.web>
<webServices>
<protocols>
<add name=HttpSoap1.2/>
<add name=HttpSoap/>
<!-- <add name=HttpPost/> -->
<!-- <add name=HttpGet/> -->
<add name=HttpPostLocalhost/>
<add name=Documentation/>
</protocols>
</webServices>
</system.web>
</configuration>
Il testo restituito dal metodo un altro messaggio SOAP dotato di propri blocchi Envelope e
Body. In questo caso, tuttavia, il corpo contiene un tag denominato NomeMetodoResponse, che a sua
volta contiene un tag annidato denominato NomeMetodoResult, il cui testo il valore di ritorno:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 381
Capitolo 29 I Web service 1057
Lattributo WebService
Lattributo WebService opzionale nel senso che ASP.NET sempre in grado di determinare se
una classe pu fungere da Web service o meno.
Il costruttore per lattributo WebService pu accettare tre argomenti: Description, Namespace
e Name. Si gi visto come utilizzare i primi due:
<WebService(Description:=A web service for converting currencies,
1058 Parte VI Applicazioni Internet
Namespace:=http://www.vb2themax.com/) > _
Public Class Converter
Inherits System.Web.Services.WebService
..
.
End Class
Il valore namespace rappresenta solo un mezzo per rendere univoco il nome del Web
service e non deve corrispondere a un URL sico. La convenzione, utilizzata praticamente
da tutti, di utilizzare lURL del sito Web della propria societ garantisce che la combinazione
namespace+nome sia univoca.
Largomento Name inuisce sul nome della classe che appare nel contratto WSDL e in ul-
timo diventa il nome della classe proxy che viene creata con il comando Add Web Reference
di Visual Studio o con la utility Wsdl.
Nel caso tale argomento venga omesso, la classe proxy assumer lo stesso nome della classe
Web service originale:
<WebService(Description:=A web service for converting currencies,
Namespace:=http://www.vb2themax.com/, Name:=MoneyConv) > _
Public Class Converter
Inherits System.Web.Services.WebService
..
.
End Class
n Le propriet Application, Session e Server restituiscono gli oggetti di ASP.NET omonimi. Log-
getto Application pu essere utilizzato per memorizzare nella cache i valori comuni a tutti i
client, proprio come accade nel caso delle applicazioni Web Form. Bisogna tener presente che
loggetto Session disponibile solo se stato specicato largomento EnableState nellattributo
WebMethod, come verr spiegato nel paragrafo Abilitare lo stato della sessione pi avanti in
questo capitolo.
n La propriet Context restituisce loggetto HttpContext per la richiesta corrente. Tale oggetto
espone i cinque oggetti intrinseci di ASP.NET, loggetto Trace, loggetto Error e qualche altra
propriet.
Tra gli oggetti pi utili ai quali possibile accedere da un Web Service vi sono Application e
Cache, con i quali possibile memorizzare i valori nella cache e condividerli tra i client.
Capitolo 29 I Web service 1059
Quello che segue un esempio molto semplice che utilizza una variabile Application per tener
traccia del numero di volte che un determinato metodo stato invocato:
<WebMethod(Description:=Return the server time)> _
Function GetTime(ByVal arg As Integer) As Date
Me.Application.Lock()
Me.Application(GetTimeCounter) = GetTimeCounter() + 1
Me.Application.Unlock()
Return Date.Now
End Function
Per riesaminare le modalit di utilizzo degli oggetti Application e Cache, si pu tornare ai pa-
ragra La classe HttpApplicationState e La classe Cache del Capitolo 26.
<WebMethod()> _
Function GetDocument(ByVal docname As String) As Document
Select Case docname.ToLower
Case invoice
Return New Invoice()
Case purchaseorder
Return New PurchaseOrder()
End Select
End Function
End Class
End Class
Lattributo SoapInclude potrebbe essere impiegato anche in altri casi simili: ad esempio,
quando un metodo accetta o restituisce un ArrayList o una collezione di oggetti, e il nome
effettivo della classe delloggetto restituito non appare esplicitamente allinterno della rma
del metodo pubblico:
Il metodo che segue accetta un ArrayList di oggetti Book ma nessun altro
metodo accetta o restituisce esplicitamente un oggetto Book.
<WebMethod(),SoapRpcMethod(),SoapInclude(GetType(Book)) > _
Sub ProcessProducts(ByVal books As ArrayList)
..
.
End Function
Lattributo WebMethod
A differenza dellattributo opzionale WebService, lattributo WebMethod obbligatorio e deve
essere utilizzato per contrassegnare tutti i metodi da rendere accessibili ai client.
Questa soluzione consente di realizzare una componente che espone un insieme di metodi ai rela-
tivi client locali, ma fa sche solo un sottoinsieme di tali metodi risulti disponibile ai client remoti
che si connettono tramite un Web Service.Tutti gli argomenti di questo attributo sono facoltativi,
ma in pratica bisognerebbe almeno specicare la descrizione per il metodo, la quale apparir nella
pagina di help del Web service:
Capitolo 29 I Web service 1061
Bufferizzare le risposte
Largomento BufferResponse assomiglia alla propriet BufferOutput delloggetto HttpResponse,
nel senso che inuisce sul fatto che loutput del Web service venga inviato al client come un unico
blocco XML oppure suddiviso in blocchi pi piccoli che vengono spediti durante la serializzazione
in XML delloggetto restituito
In pratica, questo argomento sar generalmente impostato a True (il valore predenito) e verr
modicato a False solo nel caso vengano restituiti blocchi XML di grandi dimensioni, come quando
si restituisce un DataSet di grandi dimensioni al client:
<WebMethod(BufferResponse:=False)> _
Public Function GetPublishers() As DataSet
..
.
End Function
Quando BufferResponse False, le estensioni SOAP per il metodo risultano disabilitate. (Per una
descrizione sulle estensioni SOAP si rimanda al successivo paragrafo L e estensioni SOAP).
<WebMethod(CacheDuration:=60)> _
Public Function GetPublishers() As DataSet
..
.
End Function
Se il metodo accetta degli argomenti, ASP.NET mantiene nella cache una versione diversa per
ogni combinazione dei valori degli argomenti. Ad esempio, il metodo seguente memorizza unim-
magine per ogni valore distinto dellargomento State che viene passato al metodo:
<WebMethod(CacheDuration:=60, MesssageName:=GetPublishersByState)> _
Public Function GetPublishers(ByVal State As String) As DataSet
..
.
End Function
<WebMethod(EnableSession:=True)> _
Function GetSessionID() As String
Return Session.SessionId
End Function
Capitolo 29 I Web service 1063
I Web Service che si basano sullo stato della sessione presentano un grave difetto. Se si collau-
da il metodo IncrementCounter dal browser utilizzando la pagina di help standard, si vedr che
ogni chiamata al metodo incrementa il risultato, provando cos che lo stato della sessione viene
correttamente mantenuto tra le diverse chiamate.A ulteriore riprova, si pu vericare che il valore
di ritorno del metodo GetSessionID non cambia, almeno no a che non si chiude il browser o la
sessione termina dopo il suo naturale timeout (di default, 20 minuti di inattivit).
Tuttavia, nel caso in cui il metodo venga invocato da unapplicazione Windows Form che utilizza
una classe proxy, occorre essere preparati a una sorpresa piuttosto spiacevole. In questo caso, infatti,
il metodo IncrementCounter restituisce sempre 1 e il metodo GetSessionID restituisce, invece,
una stringa diversa a ogni invocazione. Per spiegare questo insolito comportamento necessario
ricordare che la gestione della sessione dipende da un cookie non persistente che viene spedito
al client quando questo invia per la prima volta una richiesta allapplicazione ASP.NET. Se il client
un browser, il cookie della sessione viene mantenuto in memoria e rinviato a ogni richiesta suc-
cessiva in modo che lapplicazione sia in grado di riconoscere il client e associarlo allopportuno
insieme di variabili Session. Se, tuttavia, il client unapplicazione Windows Form, il cookie della
sessione non potr essere memorizzato e ogni nuova richiesta del client verr trattata come se
fosse la prima. Nel paragrafo La propriet CookieContainer pi avanti in questo capitolo, sar
possibile vedere come aggirare questo problema.
transazione.
ContextUtil.SetAbort()
Comunica al client che si verificato un problema.
Return False
Finally
In tutti i casi, chiude la connessione.
cn.Close()
End Try
End Function
Contrassegnando il metodo con lattributo AutoComplete, la transazione verr annullata nel caso
il metodo sollevi uneccezione, altrimenti verr eseguito il commit. In questo caso sar possibile
omettere la chiamata ai metodi SetComplete e SetAbort.
Nei paragra che seguono, si vedr come effettuare chiamate sincrone e asincrone a un
Web Service.
Dal momento che il metodo LengthyMethodCall del Web service SampleService.asmx contiene
semplicemente un metodo Thread.Sleep, possibile utilizzarlo per vericare il comportamento
della classe proxy quando la chiamata va in timeout.
(Nella classe Web service del server ...)
<WebMethod(Description:=A lengthy method)> _
Sub LengthyMethodCall(ByVal seconds As Integer)
Attende un numero specificato di secondi.
Thread.Sleep(seconds * 1000)
End Sub
La propriet Timeout pu essere utilizzata in concomitanza con la propriet Url per fornire
indirizzi alternativi. Si supponga, ad esempio, di avere un elenco di Web service che offrono la
stessa funzionalit (e possiedono lo stesso contratto WSDL).
1066 Parte VI Applicazioni Internet
A questo punto, possibile invocare il Web service predefinito il cui URL viene memo-
rizzato nel WSDL e consumato nel codice sorgente della classe proxy dopo aver impostato
un opportuno timeout.
Se il tempo specificato viene superato, possibile fare in modo che la propriet Url punti
a un Web Service alter nativo e ritentare la chiamata al metodo.
La classe proxy espone, inoltre, il metodo Abort che consente di cancellare una chiamata
sincrona a un metodo di un Web Service.
Dal momento che una chiamata sincrona blocca il thread, possibile invocare il metodo
Abort solo da un altro thread, quindi questa tecnica ha senso nel caso lapplicazione possieda
almeno due thread. (Se si crea un thread aggiuntivo soltanto per invocare il metodo Abort,
sarebbe bene valutare la possibilit di invocarlo, invece, in modalit asincrona).
Se si elimina una chiamata sincrona, il thread corrispondente ricever un oggetto We-
bException.
Il metodo Abort consente di cancellare una chiamata sincrona a un metodo a seconda di
fattori che non dipendono dalla scadenza del timeout, come nellesempio che segue:
(Nella classe proxy del client ...)
Dim service As New localhost.SampleService
Sub RunTheMethod()
Esegue un altro thread.
Dim tr As New Thread(AddressOf CallWebMethod)
tr.Start()
Annulla la chiamata al metodo quando una condizione diventa vera, ad
esempio quando un file viene aggiornato o un processo giunge a termine.
...
(In questo esempio si attende solo qualche secondo).
Thread.Sleep(5000)
service.Abort()
End Sub
Sub CallWebMethod()
Try
Invoca un metodo che impiega 10 secondi per giungere a termine.
service.LengthyMethodCall(10)
Catch ex As WebException
If ex.Status = WebExceptionStatus.RequestCanceled Then
La chiamata al metodo stata cancellata.
..
.
End If
End Try
End Sub
Sub CallAsyncMethod()
Dim service As New localhost.SampleService
Invoca un metodo lento che impiega circa 5 secondi per giungere a
termine. Si noti che loggetto proxy viene passato nel terzo argomento.
Dim ar As IAsyncResult = service.BeginLengthyMethodCall(5, _
AddressOf MethodCallback, service)
End Sub
If ar.IsCompleted Then
Se il metodo giunto a termine, completa la chiamata.
service.EndLengthyMethodCall(ar)
Else
Altrimenti esegue la conversione a WebClientAsyncResult per abortire la chiamata.
DirectCast(ar, WebClientAsyncResult).Abort()
End If
Metodi unidirezionali
Un caso particolare di chiamata asincrona si verica quando al client non interessa effettivamente
il valore restituito dal Web service. Ad esempio, si potrebbe realizzare un Web Service che espone
uno o pi metodi che i client invocano solo per segnalare che si vericato qualcosa o per eseguire
dei comandi (tipo lanciare la compilazione di un batch molto lungo). In situazioni di questo tipo,
possibile raggiungere lasincronia perfetta contrassegnando il metodo del Web service utilizzando
lattributo SoapDocumentMethod con il relativo argomento OneWay impostato a True:
<WebMethod(), SoapDocumentMethod(OneWay:=True)> _
Sub OneWayLengthyMethodCall(ByVal seconds As Integer)
Simula un metodo che impiega molto tempo.
Thread.Sleep(seconds * 1000)
End Sub
I metodi cos contrassegnati non devono possedere n un valore di ritorno n argomenti ByRef.
Oltre a ci, tali metodi non possono accedere ai relativi oggetti HttpContext, e ogni propriet
della classe Web service restituisce Nothing.
1068 Parte VI Applicazioni Internet
Se il client al di qua di un server proxy, le invocazioni a un Web service possono fallire. In gran
parte dei casi, si pu risolvere questo problema creando un oggetto System.Net.WebProxy e asse-
gnandolo alla propriet Proxy delloggetto proxy del client:
Dim service As New localhost.SampleService
True significa che si vuole bypassare il proxy per indirizzi locali.
service.Proxy = New WebProxy(http://proxyserver:80, True)
service.LengthyMethodCall(10)
La propriet CookieContainer
Come si visto prima nel precedente paragrafo A bilitare lo stato della sessione, la classe
proxy standard non funziona bene con i metodi dei Web Service che si basano sullo stato della
sessione ossia, quelli per cui lattributo EnableSession impostato a True in quanto tale
classe non in grado di fungere da contenitore per i cookie. La classe proxy, pertanto, non pu
memorizzare il cookie della sessione che ASP.NET invia al client quando viene individuata una
nuova sessione.
Fortunatamente, per rendere la classe proxy un contenitore adatto ai cookie basta vericare
che la propriet CookieContainer contenga un riferimento a un oggetto System.Net.CookieC
ontainer:
Dim service As New localhost.SampleService()
Sub CallStatefulWebServiceMethod()
Dim service As New localhost.SampleService()
Rende loggetto proxy un contenitore per i cookie.
service.CookieContainer = New CookieContainer()
Questa tecnica pu essere applicata a due situazioni importanti. Innanzitutto, due applicazioni
Windows Form possono condividere la stessa sessione, indipendentemente dal fatto che siano in
esecuzione sulla stessa macchina o su macchine diverse sempre che esista un mezzo per scam-
biare il contenuto del cookie della sessione. In secondo luogo, tutte le pagine di unapplicazione
Web Form ASP.NET possono invocare i metodi di un Web Service e condividere lo stesso insieme
di variabili Session. (In questo caso, le singole pagine .aspx dovrebbero salvare il cookie in una
variabile di tipo Session o Application).
Le eccezioni SOAP
I metodi dei Web Service sono in grado di sollevare eccezioni, sia direttamente tramite unistruzione
Throw, sia indirettamente nel caso eseguano unoperazione non ammessa. Quando si interagisce
con un Web Service, necessario tenere in considerazione altri tipi di errori, come quelli generati
da un client che utilizza una versione obsoleta del contratto WSDL (e della classe proxy).
Ogni volta che si verica un errore mentre un Web Service elabora una richiesta, il messaggio
SOAP restituito al client contiene un blocco <soap:Fault> allinterno del corpo invece del consueto
blocco <soap:Body>.
Dal punto di vista del client, questo blocco fault viene trasformato in un oggetto SoapExcep-
tion, il quale rappresenta il tipo da cercare nella clausola Catch del blocco TryEnd Try. La classe
SoapException eredita da SystemException tutte le propriet usuali, alle quali ne aggiunge alcune
speciche. I membri pi signicativi della classe SoapException sono:
n Code Un oggetto XmlQualiedName, il quale specica il codice di errore SOAP che descrive
la causa generale dellerrore.
n Detail Un oggetto di tipo XmlNode che rappresenta informazioni sugli errori specici per
lapplicazione. Questa propriet viene impostata solo se lerrore si vericato quando il Web
service sta elaborando il corpo del messaggio, ed Nothing in tutti gli altri casi (come quando
il problema riguarda lintestazione o il formato del messaggio).
1070 Parte VI Applicazioni Internet
Nella maggior parte dei casi, le propriet sulle quali bisognerebbe rivolgere lattenzione sono
Message e Code. Un piccolo problema dovuto al fatto che la stringa derrore originale nascosta
allinterno del valore restituito dalla propriet Message.
Ad esempio, se il metodo del Web service solleva una NullReferenceException, questa la
stringa che verr trovata nella propriet Message:
System.Web.Services.Protocols.SoapException: Server was unable to
process request. > System.NullReferenceException: Object reference
not set to an instance of an object.
at MoneyConverter.SampleService.ThrowAnException()
End of inner exception stack trace
Il modo pi semplice per estrarre il nome delleccezione vera e propria consiste nellutilizzare
le regular expression. Quella che segue una routine riutilizzabile che esegue proprio questa
operazione:
Estrae il nome delleccezione interna.
Function GetWSException(ByVal ex As SoapException) As String
Analizza la propriet Message delleccezione.
Dim mc As MatchCollection = Regex.Matches(ex.Message, > ([^:]+):)
If mc.Count >= 1 Then
stata trovata una corrispondenza il primo gruppo contiene il
valore.
Return mc.Item(0).Groups(1).Value
End If
End Function
La propriet Code della classe SoapException utile in quanto consente di classicare il tipo
di eccezione ricevuta. Questa propriet restituisce un oggetto XmlQualiedName, la cui propriet
Name pu assumere uno dei seguenti valori:
n Client La richiesta del client non formattata in modo opportuno oppure non contiene
le informazioni appropriate. Questa eccezione indica che il messaggio non dovrebbe essere
inviato nuovamente se non si apporta alcuna modica.
Capitolo 29 I Web service 1071
n Server Si vericato un errore sul server ma non stato provocato dal contenuto del
messaggio; si tratta del valore restituito dalla propriet Code quando il codice in esecuzione
allinterno del Web service solleva uneccezione.
Per agevolare la verica della propriet Code, la classe SoapException denisce quattro
costanti denominate VersionMismatchFaultCode, MustUnderstandFaultCode, ClientFaultCode
e ServerFaultCode.
Il codice che segue utilizza tali costanti per determinare il tipo di errore che si vericato,
utilizzando la clausola When del blocco TryEnd Try:
Try
Dim service As New localhost.SampleService()
service.ThrowAnException()
Catch ex As SoapException _
When ex.Code.Equals(SoapException.VersionMismatchFaultCode)
stato utilizzato un namespace non ammesso.
..
.
Catch ex As SoapException _
When ex.Code.Equals(SoapException.ServerFaultCode)
stato utilizzato un namespace non ammesso.
..
.
End Try
Argomenti avanzati
Nellultima parte di questo capitolo, verranno trattati tre argomenti avanzati che possono
rivelarsi utili nellimplementazione di Web service reali: le intestazioni SOAP, la sicurezza e
le estensioni SOAP.
Le intestazioni SOAP
Come si visto nel paragrafo I protocolli dei Web Service allinizio del capitolo, un messaggio
SOAP pu contenere informazioni aggiuntive allinterno della propria intestazione.
Lintestazione del messaggio, tuttavia, opzionale ed questo il motivo per cui tutti gli esempi
visti no a questo punto ne erano sprovvisti.
Ho creato un esempio che mostra come passare unintestazione SOAP e in che modo un
Web Service possa servirsene per trasformare date e informazioni di altro tipo nel formato
atteso dal client.
Per creare una classe Web service che funzioni con unintestazione SOAP, necessario:
1. Denire una classe che contenga i campi che si vogliono passare tramite lintestazione. Questa
classe deve derivare da System.Web.Services.Protocols.SoapHeader.
2. Denire un campo pubblico nella classe che ha lo stesso tipo di quella denita al passo pre-
cedente.
3. Aggiungere un attributo SoapHeader a tutti i metodi ai quali si vuole far leggere lintestazione
SOAP. Largomento di tale attributo il nome del campo denito al passo precedente.
1072 Parte VI Applicazioni Internet
Quello che segue un esempio di Web service che espone un metodo denominato
GetClientTime, il quale restituisce lora locale del client formattata in base alla lingua del
client. Le informazioni sulla lingua e il numero di fusi orari dal Universal Time Coordinates
(UTC) vengono passati in unistanza della classe UserInfoHeader:
Public Class SampleService
Inherits System.Web.Services.WebService
<WebMethod(), SoapHeader(userInfo)> _
Function GetClientTime() As String
Lora locale del server espresso in Coordinated Universal Time.
Dim serverTime As Date = Date.Now.ToUniversalTime
Converte nel fuso orario del client.
Dim clientTime As Date = serverTime.AddHours(userInfo.TimeOffset)
Crea un oggetto CultureInfo con informazioni appropriate sulla
lingua.
Dim ci As New System.Globalization.CultureInfo(userInfo.Culture)
Restituisce lora formattata applicando le regole del client.
Return clientTime.ToString(ci)
End Function
End Class
Quando si crea il contratto WSDL e lo si utilizza per realizzare la classe proxy, viene gene-
rata anche la denizione del client per la classe UserInfoHeader, in modo che il codice client
possa creare unistanza di tale classe e inizializzarla alloccorrenza.
Oltre a ci, la classe proxy viene estesa con un campo denominato headerclassValue
UserInfoHeaderValue, in questo esempio in modo che il client possa assegnare loggetto
UserInfoHeader a questo campo. Ecco come si invoca un metodo di un Web service che ac-
cetta un header SOAP:
Crea unintestazione.
Dim userInfo As New localhost.UserInfoHeader()
Questo lidentificatore per la lingua italiana.
userInfo.Culture = it-it
Lorario in Italia unora avanti rispetto a quella di Greenwich.
userInfo.TimeOffset = 1
Con .NET 1.0, lattributo SoapHeader rende lintestazione obbligatoria e il client ottiene unecce-
zione nel caso nessuna intestazione risulti associata alloggetto proxy; comunque possibile rendere
lintestazione opzionale aggiungendo un argomento Required impostato a False. Con .NET 1.1, lar-
gomento Required viene ignorato e le intestazioni SOAP vengono sono sempre opzionali. Perci, in
.NET 1.1 si deve garantire che il codice nel metodo del Web service funzioni correttamente anche
se il client non invia unintestazione oppure si deve sollevare una eccezione se il client non invia una
intestazione strettamente necessaria per processare linvocazione del metodo:
In .NET 1.1 si pu omettere largomento Required.
<WebMethod(), SoapHeader(userInfo, Required:=False)> _
Function GetClientTime() As String
Fornisce un oggetto userInfo predefinito se il client lha omesso.
If userInfo Is Nothing Then
userInfo = New UserInfoHeader()
In questo caso, al client viene restituita lora UTC.
End If
..
.
End Function
Per default, le intestazioni vengono inviate al Web service ma non vengono restituite al client,
per risparmiare banda. In altre parole, loggetto header funge da argomento di solo input per
il metodo del Web service. Tuttavia, questo comportamento non obbligatorio ed possibile
controllare in modo preciso le modalit di invio e ricezione delle intestazioni. La chiave di tale
funzionalit largomento Direction dellattributo SoapHeader: il valore predenito In, ma pu
assumere anche i valori InOut o Out. Il .NET Framework 1.1 supporta un quarto valore, Fault;
questo valore si pu aggiungere a uno degli altri tre se si vuole restituire lintestazione SOAP al
client anche quando il Web service solleva uneccezione.
<WebMethod(), SoapHeader(userInfo, Required:=False, _
Direction:=SoapHeaderDirection.InOut)> _
Function GetClientTime() As String
Fornisce un oggetto userInfo predefinito se il client lha omesso.
If userInfo Is Nothing Then
(Questa intestazione viene restituita al client).
userInfo = New UserInfoHeader()
userInfo.Culture = EN-US Inglese americano
userInfo.TimeOffset = -5 Fuso orario dellAmerica orientale
End If
...
End Function
Nel paragrafo Autenticazione personalizzata pi avanti verranno mostrati altri utilizzi delle
intestazioni SOAP.
Unaltra conseguenza della natura programmatica dei Web Service: possibile utilizzare
lautenticazione tramite form e reindirizzare tutte le richieste non autenticate a un determinato
URL (utilizzando lelemento LoginUrl del le web.cong), ma questa tattica non ha alcun utilizzo
pratico in quanto il client del Web service riceverebbe HTML invece del messaggio SOAP che sta
aspettando. Sebbene sia possibile eliminare questo ostacolo fornendo una funzione personalizzata
che crea manualmente il cookie di autenticazione, in generale lautenticazione tramite form non
rappresenta un metodo adatto per i Web Service, e pertanto non verr descritta in questo libro.
Lautenticazione di Windows
Lautenticazione di Windows rappresenta il metodo di autenticazione pi semplice utilizzabile
con un Web Service che non pu accettare richieste anonime. Indipendentemente dal fatto che
le pagine siano state protette con lautenticazione di Windows di tipo Basic, Digest o Integrated,
possibile fornire le credenziali a IIS assegnando un oggetto NetworkCredential alla propriet
Credential delloggetto proxy, come nel seguente frammento di codice:
Crea un oggetto System.Net.NetworkCredential.
Dim nc As New NetworkCredential()
nc.UserName = username
nc.Password = userpwd
nc.Domain = domainname
Autenticazione personalizzata
Come accade nel caso di applicazioni ASP.NET standard, lautenticazione di Windows presenta un
inconveniente piuttosto pesante: spesso non si desidera creare un account di Windows per ogni
utente che utilizza il Web service. In questo paragrafo, mostrer come sfruttare le intestazioni
SOAP per implementare un meccanismo di autenticazione personalizzato.
Lidea semplice: ogni chiamata proveniente dallapplicazione client deve includere uninte-
stazione SOAP contenente username e password. Tutti i metodi dei Web Service che richiedono
Capitolo 29 I Web service 1075
<WebMethod(), SoapHeader(accountInfo)> _
Function ProtectedMethod() As Boolean
Verifica che le credenziali siano corrette, altrimenti solleva
uneccezione.
ValidateAccount()
Restituisce un valore (in questo esempio sempre True).
Return True
End Function
Il codice lato client assomiglia a quello visto in precedenza nel paragrafo L e intestazioni SOAP
di questo capitolo:
Crea lintestazione SOAP con informazioni sullaccount.
Dim accountInfo As New localhost.AccountInfoHeader()
accountInfo.UserName = JoeDoe
accountInfo.Password = jdpwd
Ora esiste un posto centralizzato nel quale possibile controllare tutti gli accessi ai Web Service,
pertanto possibile modicare la sottoscrizione e applicare la policy senza apportare modiche
al codice di qualsiasi altro metodo del Web service.
possibile migliorare ulteriormente questo meccanismo creando unestensione SOAP che legga
lintestazione SOAP ed effettui lautenticazione e lautorizzazione prima che il usso dellesecuzione
abbia raggiunto il metodo, come si vedr nel prossimo paragrafo.
Le estensioni SOAP
Larchitettura dei Web Service pu essere completamente ridenita dal programmatore, se neces-
sario, per mezzo delle estensioni SOAP.
Unestensione SOAP un modulo software in grado di eseguire codice personalizzato prima che
il messaggio SOAP raggiunga il metodo del Web service.
Pi precisamente, esistono quattro punti nei quali unestensione SOAP ha la possibilit di eseguire
codice personalizzato.
Questi corrispondono ai quattro stati nei quali si pu trovare un messaggio SOAP. (Si veda la parte
di destra della Figura 29-9).
n BeforeDeserialize Il messaggio SOAP stato ricevuto dal client, si trova sempre in formato
XML e non ancora stato deserializzato in un oggetto in memoria.
1078 Parte VI Applicazioni Internet
La cosa interessante che un messaggio SOAP che lascia un client ossia, loggetto proxy sul client
subisce gli stessi quattro stati, ma in ordine diverso. (Si veda la parte sinistra della Figura 29-9).
BeforeSerialize BeforeDeserialize
XML
AfterSerialize AfterDeserialize
Applicazione XML Web
Client service
AfterDeserialize AfterSerialize
XML
BeforeDeserialize BeforeSerialize
Internet
Figura 29-9 I quattro passaggi a cui sottoposto un messaggio SOAP sul server e sul client
Unestensione SOAP pu leggere i dati XML che vengono inviati dal client al server e viceversa
e, se necessario, modicarli. Cos, ad esempio, si potrebbe salvare lXML per motivi di registrazione
o di debugging oppure criptare i valori di ritorno per evitare ascolti indesiderati e manomissioni,
anche se questultima operazione richiederebbe unestensione SOAP in esecuzione sul client per
decriptare il risultato.
Dal punto di vista di uno sviluppatore, unestensione SOAP non altro che una classe che deriva
dalla classe astratta System.Web.Services.Protocols.SoapExtension e ne ridenisce alcuni metodi.
Tuttavia, prima di realizzare questa classe, bisogna capire come associare unestensione SOAP a
un Web Service. A questo proposito i metodi da utilizzare sono due:
n Aggiungere una voce al le di congurazione. (In questo caso, lestensione SOAP viene invocata
per tutti i metodi associati allattributo WebMethod).
n Denire un attributo personalizzato e utilizzarlo per contrassegnare solo i metodi per i quali
si vuole attivare lestensione SOAP. Questo metodo risulta pi essibile in quanto possibile
passare uno o pi argomenti al costruttore dellattributo personalizzato e rendere questi di-
sponibili alla classe dellestensione SOAP. (Questo meccanismo potrebbe essere utilizzato, ad
esempio, per associare una tariffa a ogni metodo di un Web Service).
possibile installare unestensione SOAP aggiungendo un tag <add> nel blocco <soapExtension-
Types> del le web.cong:
<configuration>
<system.web>
<webServices>
<soapExtensionTypes>
<add type=Logger.LoggerExtension, logger
Priority=1" Group=0" />
<soapExtensionTypes>
<webServices>
<system.web>
<configuration>
dove type il nome della classe dellestensione SOAP, Group il gruppo dellestensione SOAP (pu
essere 0 o 1) e Priority la priorit allinterno del gruppo (0 la priorit pi alta). Unestensione
SOAP pu appartenere a uno dei tre gruppi:
n Group 0 Le estensioni SOAP di questo gruppo hanno la priorit maggiore e possono elabo-
rare il messaggio prima di quelle che appartengono agli altri due gruppi.
n Medium group Estensioni SOAP attivate tramite attributi personalizzati applicati ai metodi
della classe Web service. (Nel prossimo paragrafo sar possibile vedere come attivare queste
estensioni).
<AttributeUsage(AttributeTargets.Method)> _
Public Class SoapCustomAuthenticationAttribute
Inherits SoapExtensionAttribute
Il costruttore dellattributo
Sub New(ByVal requiredSubscriptionLevel As Integer)
Me.RequiredSubscriptionLevel = requiredSubscriptionLevel
End Sub
Class SoapCustomAuthentication
Inherits SoapExtension
A differenza del metodo GetInizializer che viene invocato solo una volta durante il tempo di
vita dellestensione SOAP il metodo Initialize viene invocato ogni volta che ASP.NET riceve una
richiesta che deve essere passata allestensione SOAP. Il metodo Initialize riceve un argomento
Object uguale al valore di ritorno del metodo GetInitializer. In questo esempio, tale valore rappre-
senta il livello di sottoscrizione minimo necessario ad accedere al metodo del Web service che
viene invocato.Tipicamente, il codice del metodo Initialize salva tale valore in una variabile privata
in modo da renderla accessibile da altri metodi della classe:
Il metodo Initialize viene invocato con il RequiredSubscriptionLevel
intero nellargomento.
Il terzo metodo ridenito ChainStream, il quale riceve lo stream utilizzato per inviare il testo
XML dal client al Web service e viceversa.
Questo pu essere lo stream originale utilizzato da ASP.NET per leggere e generare i dati, oppure
uno stream creato da unaltra estensione SOAP. Il metodo ChainStream viene invocato prima che
il messaggio SOAP passi attraverso ciascun stadio di elaborazione.
Dal momento che lo scopo della maggior parte delle estensioni SOAP quello di leggere e
possibilmente modicare lXML che viene trasmesso da e verso il client, necessario creare un
nuovo stream, salvare questultimo e quello originale in una coppia di variabili locali e restituire
lo stream appena creato:
Dim oldStream As Stream, newStream As Stream
Lultimo metodo ridenito che ASP.NET invoca allinterno dellestensione SOAP anche il pi
importante del gruppo in quanto implementa lazione vera e propria. Il metodo ProcessMessage
viene invocato una sola volta per ogni possibile stato del messaggio, ed possibile specicare i
diversi stati tramite la propriet Stage delloggetto SoapMessage passato come argomento:
Capitolo 29 I Web service 1083
Case SoapMessageStage.AfterDeserialize
Il messaggio viene deserializzato, pertanto possibile leggere
le intestazioni del messaggio.
If EvalSubscriptionLevel(message) < RequiredSubscriptionLevel Then
Solleva uneccezione se il livello di sottoscrizione non
adeguato.
Throw New SoapException(Insufficient subscription level, _
SoapException.ClientFaultCode)
End If
Case SoapMessageStage.BeforeSerialize
Case SoapMessageStage.AfterSerialize
Copia dal nuovo stream a quello vecchio.
newStream.Position = 0
CopyStream(newStream, oldStream)
End Select
End Sub
Try
Questa chiamata ha successo solo se lutente JoeDoe ha un livello
di sottoscrizione maggiore o uguale a 2.
Dim res As Boolean = service.YetAnotherProtectedMethod()
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Critical)
End Try