Sei sulla pagina 1di 38

29

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

Introduzione ai Web Service


Invece di illustrare per prima la teoria dei Web Service, comincer col mostrare come creare un
semplice Web service e accedervi da un programma Microsoft Visual Basic .NET. Come accade
per qualsiasi applicazione .NET, possibile realizzare un Web service utilizzando Notepad e gli
strumenti a riga di comando forniti dal .NET Framework. Tuttavia, Microsoft Visual Studio .NET
rende tutto talmente semplice che difcile resistere alla tentazione di utilizzarlo. Nei paragra
che seguiranno, mostrer come creare un semplice Web service per eseguire la conversione da
dollari in euro e viceversa. (Per semplicare il tutto, ho inserito il tasso di cambio direttamente
nel codice).

Creare il progetto Web Service


Si avvia Visual Studio e si crea un nuovo progetto ASP.NET Web service denominato MoneyCon-
verter, mostrato in Figura 29-1.Visual Studio crea una sottodirectory della directory principale di
IIS e le attribuisce lo stesso nome del progetto, proprio come accade per un progetto ASP.NET
Web Form. Analogamente alle applicazioni Web Form, il nuovo progetto contiene i propri le
web.cong e global.asax. Tuttavia, invece del le WebForm1.aspx, contiene un le denominato
Service1.asmx.

Figura 29-1 Creazione di un progetto ASP.NET Web service

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

#Region Web Services Designer Generated Code


..
.
#End Region

<WebMethod(Description:=Convert from Euro to Dollar currency)> _


Function EuroToDollar(ByVal amount As Decimal) As Decimal
Return amount * GetEuroToDollarConversionRate()
End Function

<WebMethod(Description:=Convert from Dollar to Euro currency)> _


Function DollarToEuro(ByVal amount As Decimal) As Decimal
Return amount / GetEuroToDollarConversionRate()
End Function

Private Function GetEuroToDollarConversionRate() As Decimal


Unapplicazione reale leggerebbe questo numero da un file o un
database.
Return 01.1@
End Function
End Class

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).

Collaudare il Web Service nel browser


Il modo pi semplice per collaudare il Web service appena creato consiste nelleseguire il pro-
getto, dopo aver vericato che Converter.asmx sia stata denita come pagina iniziale.Visual Studio
1050 Parte VI Applicazioni Internet

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

Figura 29-3 Si pu invocare un metodo di un Web service dallinterno del browser

Figura 29-4 Il valore di ritorno di un metodo di un Web service formattato in XML

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.

Creare un client per un Web Service


Una volta vericato che il Web service MoneyConverter funziona correttamente allinterno di
un browser, si pu passare alla creazione di unapplicazione client che lo utilizzi.
Ogni applicazione in grado di inviare una richiesta HTTP pu fungere da client per il Web
service, e non necessario che sia unapplicazione .NET o Windows.

Se ci si limita a considerare esclusivamente le applicazioni gestite, un tipico client per un


Web Service pu essere: unapplicazione Windows Form, unapplicazione Web Form oppure
unaltra applicazione Web service.
Nei paragra che seguiranno, mostrer come creare un client Windows Form, ma si seguir la
stessa procedura quando si creano altri tipi di client.

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

Figura 29-5 La nestra di dialogo Add Web Reference

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

Public Function EuroToDollar(ByVal amount As Decimal) As Decimal


Dim results() As Object = Me.Invoke(EuroToDollar, New Object() {amount})
Return CType(results(0), Decimal)
End Function

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

Private Sub btnToDollars_Click(ByVal sender As Object, ByVal e As EventArgs) _


Handles btnToDollars.Click
Dim conv As New localhost.Converter() Crea il proxy.
Dim value As Decimal = CDec(txtEuros.Text) Ottiene largomento.
Dim result As Decimal = conv.EuroToDollar(value) Invoca il metodo remoto.
txtDollars.Text = result.ToString Visualizza il risultato.
End Sub

Figura 29-8 Lapplicazione client demo

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.

I protocolli dei Web Service


I Web Service realizzati con ASP.NET supportano tre protocolli: HTTP GET, HTTP POST e SOAP.
Per migliorare la sicurezza, tuttavia, la versione 1.1 del .NET Framework disabilita il protocollo
GET e lascia abilitato il protocollo POST solo per le invocazioni locali (per poter collaudare i Web
service da browser).
Di seguito riportata la parte dei le machine.cong che denisce quali protocolli vengono sup-
portati. (Si noti che i protocolli HttpPost e HttpGet sono commentati).
1056 Parte VI Applicazioni Internet

<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>

Modicando il le machine.cong o ridenendo queste impostazioni in un le locale Web.cong,


si possono riabilitare i protocolli GET e POST . (Per disabilitare un protocollo abilitato nel le
machine.cong si utilizza il tag <remove> nel le Web.cong.) Tuttavia, sar raramente necessario
farlo, poich in pratica si utilizzer il protocollo SOAP in quasi tutte le applicazioni.
I protocolli HTTP GET e POST, infatti, non sono abbastanza potenti per gran parte delle appli-
cazioni reali. Infatti, impediscono di passare strutture e oggetti come argomenti, e non consentono
nemmeno di passare argomenti ByRef (anche se lecito restituire un oggetto purch possa essere
serializzato in XML).
Il protocollo SOAP, daltro canto, utilizza messaggi SOAP sia per gli argomenti di input sia per il
valore di ritorno e non presenta nessuna delle limitazioni dei protocolli HTTP GET e POST. Pertanto,
nel resto del capitolo ci concentreremo esclusivamente sul protocollo SOAP.
Il nodo radice di ciascun messaggio SOAP rappresenta linvolucro (envelope) del messaggio,
al cui interno contenuto il corpo (body) del messaggio; a sua volta, questultimo contiene un
tag XML avente lo stesso nome del metodo di destinazione, e tutti gli argomenti vengono inviati
allinterno di tale blocco. Ad esempio, quello che segue il testo inviato al Web service quando
viene invocato il metodo DollarToEuro per mezzo del protocollo SOAP:
POST /MoneyConverter/Converter.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: 341
SOAPAction: http://tempuri.org/DollarToEuro

<?xml version=1.0" encoding=utf-8"?>


<soap:Envelope xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd=http://www.w3.org/2001/XMLSchema
xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/>
<soap:Body>
<DollarToEuro xmlns=http://tempuri.org/>
<amount>110</amount>
</DollarToEuro>
</soap:Body>
</soap:Envelope>

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

<?xml version=1.0" encoding=utf-8"?>


<soap:Envelope xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd=http://www.w3.org/2001/XMLSchema
xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/>
<soap:Body>
<DollarToEuroResponse xmlns=http://tempuri.org/>
<DollarToEuroResult>100</DollarToEuroResult>
</DollarToEuroResponse>
</soap:Body>
</soap:Envelope>
Ciascun messaggio SOAP pu contenere unintestazione opzionale, nella quale potrebbero
esserci informazioni aggiuntive non necessariamente correlate al metodo che viene invocato.
Ad esempio, un client potrebbe utilizzare lintestazione per inviare le proprie credenziali in modo
che il Web service possa registrare chi accede ai propri metodi e quando. Il corpo di un messaggio
SOAP pu essere sostituito da una sezione <soap:Fault> se il messaggio contiene informazioni
derrore, come nel caso venga invocato un metodo passando un numero errato di argomenti o
quando il codice del metodo ha sollevato uneccezione.
SOAP lunico protocollo in grado di passare oggetti e strutture come argomenti. Lunico
requisito perch un oggetto possa essere passato a un Web Service o restituito da questo che
sia serializzabile in XML (Per ulteriori dettagli sulla serializzazione XML si consulti la nota alla ne
del Capitolo 23). Inoltre, solo il protocollo SOAP gestisce gli argomenti di output. In altre parole,
se il metodo accetta uno o pi argomenti ByRef, possibile invocarlo solo tramite il protocollo
SOAP.

I Web Service visti da vicino


Nei paragra seguenti, sar possibile vedere come migliorare le prestazioni dei Web Service tramite
il caching, come gestire le eccezioni e come sfruttare i vantaggi dellinfrastruttura di ASP.NET.

La classe Web service


La classe che espone i metodi dei Web Service in genere deriva dalla System.Web.Services
.WebService, anche se ci, in realt, non obbligatorio in quanto la presenza dellattributo
WebMethod gi sufciente a far s che uno o pi metodi della classe possano essere invocati
da client remoti.
In pratica, tuttavia, si preferisce creare sempre una classe Web service che derivi dalla classe
di base WebService perch tale soluzione consente di accedere al contesto di ASP.NET e ad
altri oggetti utili, come le variabili di tipo Application e Session.

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

Accedere agli oggetti di ASP.NET


Uno dei vantaggi di creare un Web Service derivando una classe da System.Web.Services.WebSer
vice la possibilit di accedere a ogni oggetto intrinseco di ASP.NET dallinterno della classe. Pi
precisamente, la classe di base WebService espone le seguenti propriet:

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 User restituisce loggetto IPrincipal che rappresenta un utente autenticato. Ad


esempio, questa propriet restituisce un oggetto WindowsPrincipal nel caso in cui lutente
venga autenticato attraverso lautenticazione di Windows. Per decidere quale meccanismo di
autenticazione debba gestire il Web service, occorre modicare il le web.cong per lappli-
cazione Web service, proprio come nel caso di applicazioni Web Form. (Per maggiori dettagli
sullautenticazione in ASP.NET, si rimanda al Capitolo 27).

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

<WebMethod(Description:=The number of times GetTime has been called)> _


Function GetTimeCounter() As Integer
Dim o As Object = Me.Application(GetTimeCounter)
If o Is Nothing Then
Return 0
Else
Return CInt(o)
End If
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.

Tipi di dato semplici e complessi


Il grande vantaggio dei Web Service la capacit di ricevere e restituire qualsiasi tipo di dato di
.NET, inclusi i tipi personalizzati, a patto che siano serializzabili in XML. Ad esempio, possibile
passare e restituire oggetti DataSet di ADO.NET sia generici sia fortemente tipizzati in quanto
possono, appunto, essere serializzati in XML.
Nella maggior parte dei casi, non occorre nulla di speciale per eseguire il marshaling di un
tipo di dato in quanto la denizione di tutti i tipi ricevuti o restituiti da un progetto Web service,
ed esposti come argomenti o valori di ritorno, inclusa nel contratto WSDL. Ogni volta che si
aggiunge un riferimento Web in Visual Studio o si esegue la routine Wsdl, viene creata una classe
proxy per ognuno di questi tipi in modo che il client possa utilizzarli. In qualche caso, tuttavia,
necessario obbligare Visual Studio o la utility Wsdl a includere i tipi corretti nel contratto WSDL.
Si consideri, ad esempio, la seguente classe Web service:
Public Class SampleService
Inherits System.Web.Services.WebService

<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

Public MustInherit Class Document


Public [Date] As Date
Public Number As Integer
1060 Parte VI Applicazioni Internet

End Class

Public Class Invoice


Inherits Document
Public Total As Decimal
End Class

Public Class PurchaseOrder


Inherits Document
Public AuthorizedBy As String
End Class

Il problema in questo listato che il metodo GetDocument pu restituire un oggetto Invoice o


un PurchaseOrder, sebbene il contratto WSDL conterr esclusivamente la denizione della classe
astratta Document.
Per obbligare il contratto WSDL a includere la denizione delle due classi concrete, necessario
contrassegnare il metodo con lattributo SoapRpcMethod e aggiungere due istanze dellattributo
SoapInclude, come segue:
<WebMethod(),SoapRpcMethod(),SoapInclude(GetType(Invoice)), _
SoapInclude(GetType(PurchaseOrder))> _
Function GetDocument(ByVal docname As String) As Document
..
.
End Function

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

Il namespace System.Xml.Serialization include altri attributi Soapxxxx, come SoapType, Soa-


pElement, SoapAttributeAttribute, SoapIgnore e SoapEnum.
Questi attributi possono essere utilizzati per denire la serializzazione di classi e strutture in
XML. A meno di non dover controllare la forma dellXML inviato o ricevuto dal Web service, non
sar necessario applicare questi attributi per poter utilizzare i Web Service.

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

<WebMethod(Description:=Convert from Euro to Dollar currency)> _


Function EuroToDollar(ByVal amount As Decimal) As Decimal
Return amount * GetEuroToDollarConversionRate()
End Function

Utilizzare i metodi di overload


Largomento MessageName specica il nome che i client possono utilizzare per invocare il meto-
do. Questo argomento necessario solo quando la classe Web service espone metodi di overload
con lo stesso nome.
Dal momento che linfrastruttura del Web service non in grado di risolvere le chiamate ai metodi
di overload, sar necessario specicare, per questi ultimi, nomi esterni differenti:
<WebMethod (Description:=Add two Integers)> _
Public Function Add(ByVal n1 As Integer, ByVal n2 As Integer) _
As Integer
Return n1 + n2
End Function

<WebMethod (Description:=Add two floating point numbers, _


MessageName:=AddDouble)> _
Public Function Add(ByVal n1 As Double, ByVal n2 As Double) _
As Double
Return n1 + n2
End Function

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).

Memorizzare i risultati nella cache


Dal momento che si tratta di applicazioni ASP.NET, i Web Service gestiscono il caching delloutput
proprio come i controlli Web Form e Web User.
I Web Service non gestiscono, per, la direttiva @OutputCache, per cui loutput di ogni singolo
metodo pu essere memorizzato nella cache per un certo intervallo di tempo tramite largomento
CacheDuration dellattributo WebMethod. Ad esempio, il codice che segue memorizza loutput
del metodo GetPublishers per un minuto:
1062 Parte VI Applicazioni Internet

<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

Se utilizzato in modo appropriato, lattributo CacheDuration in grado di migliorare le pre-


stazioni del Web service pi di qualsiasi altra tecnica disponibile. Tuttavia, sarebbe bene valutare
sempre con attenzione limpatto sul consumo delle risorse sul server.
Questa tecnica risulta particolarmente efcace per risultati di piccole dimensioni che richie-
dono una notevole elaborazione sul server ad esempio, il calcolo di statistiche sui database ma
possono diventare un collo di bottiglia nel caso in cui il metodo accetti argomenti i cui valori
possono variare considerevolmente o restituisca una grande quantit di dati.

Abilitare lo stato della sessione


I Web Service, come tutte le applicazioni .NET, possono sfruttare i vantaggi offerti dalla gestione
dello stato della sessione, anche se questa, di norma, risulta disabilitata in quanto la maggior parte
delle chiamate ai Web Service sono prive di stato e non hanno bisogno di memorizzare i dati
allinterno di una variabile Session. Questa soluzione evita la necessit di popolare la collezione
Session a ogni chiamata, riduce il consumo di memoria sul server e impedisce lafnit di server.
Qualche volta, tuttavia, pu risultare utile o interessante abilitare le variabili Session: ad esempio,
in un Web Service che espone un metodo per ordinare i singoli prodotti e un altro metodo per
confermare lordine, calcolare il costo della spedizione e restituire il totale dellacquisto.
Un Web service di questo tipo non privo di stato e deve memorizzare i dettagli dellordine
in corso in una o pi variabili Session. Per abilitare la gestione della sessione occorre imposta-
re largomento EnableSessione dellattributo WebMethod a True; se questo argomento viene
omesso, oppure impostato a False, ogni riferimento alloggetto Session sollever uneccezione
NullReference. Quello che segue un semplice esempio di un metodo di un Web service basato
sulle variabili Session:
<WebMethod(EnableSession:=True)> _
Function IncrementCounter() As Integer
If Session(counter) Is Nothing Then
Session.Add(counter, 1)
Else
Session(counter) = CInt(Session(counter)) + 1
End If
Return CInt(session(counter))
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.

Creare Web service transazionali


Largomento TransactionOption dellattributo WebMethod consente di creare Web service transa-
zionali, in grado di invocare componenti COM+ transazionali e sfruttare i vantaggi del supporto
offerto al Microsoft Distribuited Transaction Coordinator (MS DTC). (Per ulteriori informazioni
sulle componenti transazionali si rimanda al Capitolo 31.)
A causa della natura priva di stato dei Web service, un metodo di un Web service pu rappresen-
tare soltanto la radice di una transazione. In altre parole, non possibile avviare una transazione in
un client che potrebbe essere unapplicazione Windows Form, una pagina Web Form o un altro
metodo di un Web service e propagarla attraverso una chiamata al Web service.
Largomento TransactionOption pu accettare cinque valori differenti Disabled, NotSuppor-
ted, Supported, Required e RequiresNew ma a causa della limitazione appena menzionata, sono
ammessi solo due comportamenti diversi. Per ottenere un Web service transazionale occorre
specicare Required o RequiresNew, mentre gli altri valori valgono per i Web service standard,
ossia non transazionali. Quella che segue la tipica struttura di un metodo di un Web service
transazionale:
<WebMethod(TransactionOption:=TransactionOption.RequiresNew)> _
Function UpdateDatabase() As Boolean
Dim cn As New System.Data.OleDbConnection(myConnString)
Try
cn.Open()
Esegue tutte le necessarie operazioni di aggiornamento.
..
.
Se tutto va a buon fine, esegue il commit della transazione.
ContextUtil.SetComplete()
Comunica al client che tutto andato a buon fine.
Return True
Catch
Se si verificato un errore di aggiornamento, annulla la
1064 Parte VI Applicazioni Internet

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.

La classe proxy del Web Service


Per default, Visual Studio .NET nasconde la classe proxy che crea. Per renderla visibile, si deve
premere il pulsante Show All Files della nestra Solution Explorer della Toolbar, espandere lele-
mento Web References e quindi espandere lelemento Reference.map. Oltre ai metodi specici che
rispecchiano quelli che il server Web espone, la classe proxy eredita diverse propriet e metodi
utili dalla classe System.Web.Protocols.SoapHttpClientProtocol.Ad esempio, si possono utilizzare
le propriet Timeout e Url per invocare un differente Web service con la stessa interfaccia, se il
Web service di default non fosse disponibile. UserAgent lintestazione user agent utilizzata nella
richiesta al Web service; il valore di default Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services
Client Protocol w.x.yyyy.z), dove w.x.yyyy.z la versione del Common Language Runtime).
Due propriet riguardano le credenziali utente. Se la propriet PreAuthenticate True, tutte
le richieste deve contenere le credenziali di autenticazione; se False (il default), le credenziali
vengono inviate solo se il Web service non permette accesso anonimi e restituisce un codice HTTP
401 quando la richiesta viene inizialmente elaborata. Se AllowAutoRedirect False (il default), la
classe proxy genera una eccezione se viene tentata un redirezione del server; questa impostazione
opportuna se il messaggio contiene informazioni di autenticazione o altre informazioni con-
denziali e un redirezionamento a un ulteriore server pu compromettere la sicurezza.

Chiamate a metodi in late-bound ed early-bound


Tutte le classi proxy ereditano dalla classe di base SoapHttpClientProtocol la capacit di invo-
care i metodi in modalit sincrona e asincrona tramite i metodi protetti Invoke, BeginInvoke
ed EndInvoke.
Oltre a questi generici metodi di late binding, Visual Studio e la utility Wsdl creano tre me-
todi fortemente tipizzati nella classe proxy per ciascun metodo della componente Web service.
Si tratta di metodi pubblici che possibile invocare dallapplicazione client. Ad esempio, il
metodo EuroToDollar del le Converter.asmx fa s che allinterno della classe proxy vengano
creati i metodi che seguono:
(Nella classe proxy del client ...)
Metodo fortemente tipizzato per chiamate asincrone.
Public Function DollarToEuro(ByVal amount As Decimal) As Decimal
Dim results() As Object = Me.Invoke(DollarToEuro, New Object() {amount})
Return CType(results(0),Decimal)
End Function
Capitolo 29 I Web service 1065

Metodo fortemente tipizzato per avviare le chiamate asincrone


Public Function BeginDollarToEuro(ByVal amount As Decimal, _
ByVal callback As AsyncCallback, ByVal asyncState As Object) _
As IAsyncResult
Return Me.BeginInvoke(DollarToEuro, _
New Object() {amount}, callback, asyncState)
End Function

Metodo fortemente tipizzato per terminare le chiamate asincrone.


Public Function EndDollarToEuro(ByVal asyncResult As IAsyncResult) As Decimal
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0),Decimal)
End Function

Nei paragra che seguono, si vedr come effettuare chiamate sincrone e asincrone a un
Web Service.

Invocare un metodo in modalit sincrona


Dal momento che si esegue una chiamata a un Web Service in esecuzione su un computer in In-
ternet, bisognerebbe sempre proteggere le chiamate asincrone con un timeout.
Se si supera il tem po specificato dalla propriet Timeout, verr sollevato un oggetto
System.Net.WebException, pertanto linvocazione al metodo andrebbe protetta con un blocco
TryEnd Try:
(Nella classe proxy del client ...)
Dim service As New localhost.SampleService
Try
Imposta un timeout di 5 secondi.
service.Timeout = 5000
Invoca un metodo che impiega 10 secondi per giungere a termine.
service.LengthyMethodCall(10)
Catch ex As WebException
If ex.Status = WebExceptionStatus.Timeout Then
Loperazione va in timeout.
..
.
End If
Catch ex As Exception
stata sollevata unaltra eccezione.
End Try

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

Invocare un metodo in modalit asincrona


Come ho mostrato in precedenza, la classe proxy espone una coppia di metodi Beginxxxx
ed Endxxxx per ogni procedura della componente Web service contrassegnata con lattribu-
to WebMethod.+Questi due metodi possono essere utilizzati per invocare il Web service in
modalit asincrona.
A questo proposito, le azioni necessarie sono identiche a quelle che occorrono per invocare
un delegate sempre in modalit asincrona.
Dal momento che i delegate sono gi stati trattati nel Capitolo 12, in questo contesto mi
limiter a fornire un esempio di chiamata asincrona a un metodo di un Web Service:
Capitolo 29 I Web service 1067

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

Questo il metodo di callback.


Sub MethodCallback(ByVal ar As IAsyncResult)
Estrae loggetto proxy dalla propriet AsyncState.
Dim service As localhost.SampleService = _
DirectCast(ar.AsyncState, localhost.SampleService)
Completa la chiamata al metodo.
service.EndLengthyMethodCall(ar)
End Sub

Annullare una chiamata asincrona a un Web Service leggermente pi complesso rispetto al


caso di una chiamata sincrona. necessario, infatti, convertire loggetto IASyncResult restituito
dal metodo Beginxxxx in un oggetto WebClientAsyncResult e quindi invocare il metodo Abort
di questultimo:
Esegue un metodo che richiede 5 secondi di esecuzione.
Dim service As New localhost.SampleService()
Dim ar As IAsyncResult = service.BeginLengthyMethodCall(5, _
Nothing, Nothing)

In questo punto eseguire qualcosaltro.


..
.

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

Gestire i Server Proxy

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()

Ogni volta che questo metodo viene invocato, il valore del


controllo Label viene incrementato di 1.
Sub TestIncrementCounterMethod()
Rende loggetto proxy un contenitore di cookie, se necessario.
If service.CookieContainer Is Nothing Then
service.CookieContainer = New CookieContainer()
End If
Label1.Text = service.IncrementCounter()
End Sub

Dopo aver impostato la propriet CookieContainer, loggetto proxy in grado di memorizzare


il cookie della sessione. La sessione termina quando loggetto proxy viene impostato a Nothing
oppure quando il timeout della sessione scade senza invocare alcun metodo del Web service. (Il
timeout predenito per la sessione di 20 minuti).
Dal momento che la propriet CookieContainer restituisce una collezione di cookie, possi-
bile leggere quelli che contiene e aggiungerne di propri. In particolare, possibile leggere uno
speciale cookie della sessione denominato ASP.NET_SessionId, salvarlo in una variabile su disco e
aggiungerlo alla collezione di cookie in una successiva chiamata:
Dim saveCookie As Cookie

Sub CallStatefulWebServiceMethod()
Dim service As New localhost.SampleService()
Rende loggetto proxy un contenitore per i cookie.
service.CookieContainer = New CookieContainer()

Se esiste gi un cookie, lo aggiunge alla collezione di cookie.


Capitolo 29 I Web service 1069

If Not (saveCookie Is Nothing) Then


service.CookieContainer.Add(saveCookie)
End If

Invoca il metodo del Web service.


Label1.Text = service.IncrementCounter()

Salva il cookie se si tratta della prima chiamata.


If saveCookie Is Nothing Then
Sostituisce largomento con lURL del Web service.
Dim cookieUri As New Uri(http://localhost)
Salva il cookie ASP.NET_SessionID che appartiene allURI localhost.
saveCookie = service.CookieContainer.GetCookies(cookieUri).Item _
(ASP.NET_SessionId)
End If
End Sub

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 Message Il messaggio delleccezione originale.

n Actor LURL del Web service che solleva leccezione.

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

possibile utilizzare la funzione GetWSException allinterno di un blocco TryEnd Try che


utilizza una clausola When, come segue:
Try
Dim service As New localhost.SampleService()
service.ThrowAnException()
Catch ex As SoapException _
When GetWSException(ex) = System.NullReferenceException
Uneccezione per un riferimento nullo
..
.
Catch ex As SoapException _
When GetWSException(ex) = System.DivideByZeroException
Uneccezione per una divisione per zero
..
.
End Try

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 VersionMismatch stato trovato un namespace non valido.

n MustUnderstand Il client ha inviato un elemento il cui attributo MustUnderstand 1, ma


il server non in grado di elaborarlo.

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

Questa la variabile pubblica che riceve lintestazione userInfo.


Public userInfo As UserInfoHeader

<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

Questa classe definisce le informazioni contenute nellintestazione SOAP.

Public Class UserInfoHeader


Inherits SoapHeader

Questo membro contiene il nome della lingua dellutente.


Public Culture As String =
Questo membro contiene la differenza rispetto allora UTC.
Public TimeOffset As Single = 0
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

Crea unistanza della classe proxy.


Dim service As New localhost.SampleService()
Assegna lintestazione al campo particolare xxxxValue.
service.UserInfoHeaderValue = userInfo
Invoca lXML Web service.
Dim res As String = service.GetClientTime()
Capitolo 29 I Web service 1073

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.

La sicurezza dei Web Service


La sicurezza dei Web Service non differisce in modo sostanziale dalla sicurezza standard di ASP.NET,
e possono essere applicati anche tutti i meccanismi di sicurezza illustrati nel Capitolo 27. La
particolarit della sicurezza dei Web Service deriva dalla natura non interattiva. Ad esempio, se
si disabilitano gli accessi anonimi per un le .asmx allinterno di IIS, ogni richiesta del client per
questi le fallisce senza per mostrare alcuna nestra di dialogo.
1074 Parte VI Applicazioni Internet

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

Crea loggetto proxy e gli assegna le credenziali dellutente.


Dim service As New localhost.Converter()
service.Credentials = nc
Effettua la chiamata.
Dim euros As Decimal = service.DollarToEuro(100)

Se la pagina .asmx richiede lautenticazione di Windows e vengono passate credenziali non


valide (oppure nessuna credenziale), lapplicazione client riceve un oggetto WebException la cui
propriet Message :
The request failed with HTTP status 401: Access Denied.

Se lapplicazione client dotata di interfaccia utente, si pu intercettare leccezione, mostrare


una casella di messaggio e chiedere allutente di immettere username e password e di effettuare
un secondo tentativo.
Tutto ci che stato detto nel Capitolo 27 sui vari metodi di autenticazione di Windows vale
anche nel caso dei Web Service. Ad esempio, quando si utilizza lautenticazione Basic, lo userna-
me e la password vengono inviati in chiaro (in formato codicato Base64), quindi bisognerebbe
utilizzare la sicurezza integrata di Windows, se possibile.

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

lautenticazione ad esempio quelli che necessitano di un qualche tipo di sottoscrizione invocano


una funzione principale, denominata ValidateUser, che controlla le credenziali contenute nellinte-
stazione e solleva uneccezione se queste non sono valide o se la sottoscrizione scaduta. Quello
che segue il codice sorgente di una classe Web service che contiene un metodo di questo tipo:
Public Class SampleService
Inherits System.Web.Services.WebService

Public accountInfo As AccountInfoHeader

<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

Convalida username e password (funzione privata).


Private Sub ValidateAccount()
Solleva uneccezione se manca lintestazione.
If accountInfo Is Nothing Then
Throw New SoapException(Missing user info header, _
SoapException.ClientFaultCode)
End If

Solleva uneccezione se i membri dellintestazione non sono


impostati.
If accountInfo.UserName = Or accountInfo.Password = Then
Throw New SoapException(Missing user info, _
SoapException.ClientFaultCode)
End If

Solleva uneccezione se lutente non valido.


If Not CheckUser(accountInfo.UserName, accountInfo.Password) Then
Throw New SoapException(Insufficient subscription level, _
SoapException.ClientFaultCode)
End If

Esce regolarmente se tutto corretto.


End Sub

Verifica che le credenziali dellutente siano valide.


Function CheckUser(ByVal username As String, ByVal password As String) _
As Boolean
(Unapplicazione reale utilizzerebbe un database).
If username = JoeDoe And password = jdpwd Then
Return True
ElseIf username = AnnSmith And password = aspwd Then
Return True
Else
Utente sconosciuto o credenziali non valide.
Return False
End If
End Function
End Class

Questa la classe SOAP Header.


1076 Parte VI Applicazioni Internet

Public Class AccountInfoHeader


Inherits SoapHeader

Public UserName As String


Public Password As String
End Class

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

Passa le informazioni sullaccount alla classe proxy.


Dim service As New localhost.SampleService()
service.AccountInfoHeaderValue = accountInfo
Invoca il metodo protetto del Web service.
Dim res As Boolean = service.ProtectedMethod()

Il vantaggio di questo approccio la possibilitdi espandere facilmente la procedura ValidateU-


ser della classe Web service per implementare sosticati criteri di autorizzazione. Ad esempio, la
procedura pu utilizzare tecniche di reection per estrarre il nome del metodo che lha invocata
ossia, il metodo del Web service invocato dal client e garantire che gli utenti possiedano un
livello di sottoscrizione che consenta loro di effettuare la chiamata. Ecco unimplementazione di
tale concetto:
Convalida username e password. (Procedura privata)
Private Sub ValidateAccount()
Solleva uneccezione se manca lintestazione.
If accountInfo Is Nothing Then
Throw New SoapException(Missing user info header, _
SoapException.ClientFaultCode)
End If

Solleva uneccezione se i membri dellintestazione non sono


impostati.
If accountInfo.UserName = Or accountInfo.Password = Then
Throw New SoapException(Missing user info, _
SoapException.ClientFaultCode)
End If

Estrae il livello di sottoscrizione di questo utente.


Dim thisUserSubscriptionLevel As Integer = GetUserSubscriptionLevel _
(accountInfo.UserName, accountInfo.Password)
Esce se le credenziali non sono valide.
If thisUserSubscriptionLevel < 0 Then
Throw New SoapException(Unknown user, _
SoapException.ClientFaultCode)
End If

Estrae il nome del metodo che ha invocato la procedura.


Dim st As New System.Diagnostics.StackTrace(False)
GetFrame(0) descrive la procedura in esecuzione,
GetFrame(1) descrive la procedura chiamante.
Dim sf As System.Diagnostics.StackFrame = st.GetFrame(1)
Dim mb As System.Reflection.MethodBase = sf.GetMethod
Capitolo 29 I Web service 1077

Estrae il livello di sottoscrizione richiesto per invocare


il metodo.
Dim requiredSubscriptionLevel As Integer
Select Case mb.Name
Case ProtectedMethod
requiredSubscriptionLevel = 1
Case AnotherProtectedMethod
requiredSubscriptionLevel = 2
End Select

Solleva uneccezione se il livello di sottoscrizione non


sufficiente.
If thisUserSubscriptionLevel < requiredSubscriptionLevel Then
Throw New SoapException(Insufficient subscription level, _
SoapException.ClientFaultCode)
End If

Esce regolarmente se tutto corretto.


End Sub

Ottiene il livello di sottoscrizione dellutente, oppure -1 se le


credenziali non sono valide.
Function GetUserSubscriptionLevel(ByVal username As String, _
ByVal password As String) As Integer
(Unapplicazione reale utilizzerebbe un database).
If username = JoeDoe And password = jdpwd Then
Return 1
ElseIf username = AnnSmith And password = aspwd Then
Return 2
Else
Utente sconosciuto o credenziali non valide.
Return -1
End If
End Function

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

n AfterDeserialize Il messaggio SOAP stato serializzato in un oggetto e il metodo del Web


service sta per essere invocato.

n BeforeSerialize Il metodo del Web service ha terminato lesecuzione e loggetto risultato


sta per essere serializzato in XML.

n AfterSerialize Loggetto risultato stato serializzato in un messaggio SOAP e il testo XML


sta per essere inviato al client.

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).

Ecco come implementare questi due metodi di attivazione.


Capitolo 29 I Web service 1079

Attivare unestensione SOAP da un file di configurazione

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).

n Group 1 Le estensioni SOAP di questo gruppo hanno la priorit minore ed elaborano il


messaggio dopo quelle che appartengono ai primi due gruppi.

Attivare unestensione SOAP modicando la classe web.cong rappresenta la soluzione migliore


quando questa deve essere applicata a tutti i metodi del Web service e quando non bisogna passarle
alcun argomento.Ad esempio, si potrebbe utilizzare questa tecnica per attivare unestensione SOAP che
tenga traccia di tutte le richieste in input o che cripti il risultato di tutti i metodi del Web service.

Attivare unestensione SOAP con un attributo personalizzato


Il secondo, e pi essibile, metodo per attivare unestensione SOAP consiste nellutilizzare un attri-
buto personalizzato per contrassegnare i metodi del Web service per i quali deve essere utilizzata tale
estensione. Questa tecnica consente di applicare lestensione solo a un sottoinsieme di tutti i metodi
esistenti e di denire gli argomenti che vengono passati allestensione SOAP metodo per metodo.
Nei paragra che seguono, sar possibile vedere come implementare unestensione SOAP che
monitorizzi gli accessi a ogni metodo del Web service contrassegnato con lattributo SoapCusto-
mAuthenticationAttribute. Il costruttore di questo attributo accetta un intero che denisce il livello
di sottoscrizione minimo richiesto perch un client possa invocare il metodo contrassegnato con
tale attributo. (Questa la versione dellestensione SOAP del metodo di autenticazione e autoriz-
zazione descritto in precedenza nel paragrafo Autenticazione personalizzata di questo capitolo).
Ad esempio, un metodo di SampleService.asmx che pu essere invocato solo dai client il cui livello
di sottoscrizione 2 o superiore dovrebbe essere:
1080 Parte VI Applicazioni Internet

<WebMethod(), SoapCustomAuthentication(2), SoapHeader(accountInfo)> _


Function YetAnotherProtectedMethod() As Boolean
..
.
End Function

La classe SoapCustomAuthenticationAttribute deve ereditare dalla classe astratta SoapEx-


tensionAttribute. Tutte le classi che derivano da questa classe astratta devono ridenire le
propriet Priority ed ExtensionType.
Questultima particolarmente importante in quanto restituisce loggetto System.Type
che denisce la classe dellestensione SOAP (denominata SoapExtensionAuthentication, in
questo caso). Nellesempio che segue, la classe dellattributo espone unulteriore propriet,
RequiredSubscriptionLevel, che denisce il livello di sottoscrizione minimo necessario a invo-
care il metodo al quale viene applicato lattributo. Il livello di sottoscrizione minimo lunico
argomento che occorre passare al costruttore dellattributo:
Lattributo personalizzato che occorre utilizzare per contrassegnare i
metodi che richiedono un particolare livello di sottoscrizione.

<AttributeUsage(AttributeTargets.Method)> _
Public Class SoapCustomAuthenticationAttribute
Inherits SoapExtensionAttribute

Il costruttore dellattributo
Sub New(ByVal requiredSubscriptionLevel As Integer)
Me.RequiredSubscriptionLevel = requiredSubscriptionLevel
End Sub

necessario ridefinire la propriet Priority.

Dim m_Priority As Integer

Public Overrides Property Priority() As Integer


Get
Return m_Priority
End Get
Set(ByVal Value As Integer)
m_Priority = Value
End Set
End Property

La propriet ExtensionType restituisce il tipo della classe


SoapExtension.

Public Overrides ReadOnly Property ExtensionType() As System.Type


Get
Return GetType(SoapCustomAuthentication)
End Get
End Property

RequiredSubscriptionLevel una propriet personalizzata per


questo attributo.

Dim m_RequiredSubscriptionLevel As Integer

Property RequiredSubscriptionLevel() As Integer


Get
Return m_RequiredSubscriptionLevel
End Get
Capitolo 29 I Web service 1081

Set(ByVal Value As Integer)


m_RequiredSubscriptionLevel = Value
End Set
End Property
End Class

La classe dellestensione SOAP


Lestensione SOAP vera e propria contenuta in una classe che deriva dalla classe astratta
System.Web.Services.Protocols.SoapExtension e ne ridenisce alcuni membri. Dal momento
che la struttura di questa classe risulta piuttosto complessa, mostrer e commenter il relativo
listato un metodo alla volta, seguendo lordine con il quale ciascuno viene invocato dallinfra-
struttura di ASP.NET.
Il metodo GetInitializer viene invocato solo una volta durante il ciclo di vita dellestensione
SOAP. Esistono due versioni di overload di tale metodo, a seconda del fatto che lestensione
venga attivata tramite una voce del le di congurazione o tramite un attributo personalizzato
nella classe del Web service. Nella prima istanza, il metodo riceve un oggetto System.Type che
corrisponde al tipo della classe del Web service. (Questo argomento necessario in quanto
unestensione SOAP pu servire pi classi di un Web Service). Nellultima istanza, il metodo
GetInitializer riceve un riferimento allattributo personalizzato e un oggetto MethodInfo che
descrive il metodo al quale lattributo deve essere applicato.
In entrambi i casi, il metodo GetInitializer si suppone che restituisca un valore Object.
Tale valore viene quindi passato come argomento al metodo Initialize. Questo approccio della
doppia inizializzazione rende il lavoro dello sviluppatore un po pi complicato ma migliora
le prestazioni. In questo esempio specico, il metodo GetInitilizer restituisce la propriet Re-
quiredSubscriptionLevel dellattributo personalizzato oppure il valore 1 se lestensione SOAP
stata attivata tramite una voce del le di congurazione:
La classe Soap extension.

Class SoapCustomAuthentication
Inherits SoapExtension

Questo metodo di overloading viene invocato se lestensione SOAP installata


in web.config; riceve il tipo della classe WebService.

Public Overloads Overrides Function GetInitializer( _


ByVal serviceType As System.Type) As Object
In questo caso, viene restituito semplicemente 1, il pi basso livello
di sottoscrizione richiesto per accedere al metodo del Web service.
If serviceType Is GetType(SampleService) Then
Return 1
End If
End Function

Questo metodo di overloading viene attivato se lestensione SOAP installata


poich il client ha invocato un metodo che stato
contrassegnato con un SoapExtensionAttribute.

Public Overloads Overrides Function GetInitializer( _


ByVal methodInfo As LogicalMethodInfo, _
ByVal attribute As SoapExtensionAttribute) As Object
Ottiene un riferimento fortemente tipizzato allattributo.
1082 Parte VI Applicazioni Internet

Dim scaAttr As SoapCustomAuthenticationAttribute = _


DirectCast(attribute, SoapCustomAuthenticationAttribute)
Restituisce la propriet RequiredSubscriptionLevel.
Return scaAttr.RequiredSubscriptionLevel
End Function

...(gli altri metodi di questa classe come verr descritto in


seguito)...
..
.
End Class

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.

Dim RequiredSubscriptionLevel As Integer

Public Overrides Sub Initialize(ByVal initializer As Object)


Salva il livello di sottoscrizione richiesto per usi futuri.
RequiredSubscriptionLevel = CInt(initializer)
End Sub

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

ChainStream viene invocato allatto dellistanziazione della classe SoapExtension.

Public Overrides Function ChainStream(ByVal stream As Stream) As Stream


Salva il vecchio stream.
oldStream = stream
Crea un nuovo stream e lo restituisce.
newStream = New MemoryStream()
Return newStream
End Function

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

Questo metodo viene invocato pi volte per ogni messaggio.


Public Overrides Sub ProcessMessage(ByVal message As SoapMessage)
Select Case message.Stage
Case SoapMessageStage.BeforeDeserialize
Copia dal nuovo stream a quello nuovo.
CopyStream(oldStream, newStream)
newStream.Position = 0

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

Questa la routine ausiliaria che sposta i dati da uno stream allaltro.

Private Sub CopyStream(ByVal source As Stream, ByVal dest As Stream)


Dim sr As New StreamReader(source)
Dim sw As New StreamWriter(dest)
sw.WriteLine(sr.ReadToEnd)
sw.Flush()
End Sub

EvalSubscriptionLevel una funzione privata che analizza tutte le intestazioni delloggetto


SoapMessage e cerca un oggetto AccountInfoHeader.

Se viene trovata unintestazione di questo tipo, la funzione pu determinare se username e


password del client sono validi e restituisce il livello di sottoscrizione del client:
Private Function EvalSubscriptionLevel(ByVal message As SoapMessage) _
As Integer
Verifica se esiste unintestazione di tipo AccountInfoHeader.
For Each header As SoapHeader In message.Headers
If TypeOf header Is AccountInfoHeader Then
Converte nel tipo appropriato.
Dim accountInfo As AccountInfoHeader = _
DirectCast(header, AccountInfoHeader)
Verifica le credenziali dellutente e restituisce il livello
di sottoscrizione.
Return GetUserSubscriptionLevel(accountInfo.UserName, _
accountInfo.Password)
End If
Next

Se si arriva a questo punto, mancano le credenziali oppure non sono


valide.
Return -1
End Function
1084 Parte VI Applicazioni Internet

( stato omesso il codice sorgente della classe GetUserSubscriptionLevel essendo identico a


quello illustrato in precedenza nel paragrafo Autenticazione personalizzata di questo capitolo).
Quello che segue il codice lato client che invoca un metodo protetto con un attributo SoapCu-
stomAuthentication:
Crea informazioni relative allaccount nellintestazione.
Dim accountInfo As New localhost.AccountInfoHeader()
accountInfo.UserName = JoeDoe
accountInfo.Password = jdpwd
Associa le informazioni sullaccount alloggetto proxy.
Dim service As New localhost.SampleService()
service.AccountInfoHeaderValue = accountInfo

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

Le estensioni SOAP lato client


Fin qui, ho descritto le cosiddette estensioni SOAP lato server, che rappresentano di gran lunga
il tipo di estensione SOAP pi comune. Tuttavia, anche possibile implementare estensioni
SOAP lato client.Ad esempio, unestensione SOAP lato client potrebbe rivelarsi necessaria per
decriptare lXML inviato da un Web Service e criptato da unestensione SOAP lato server.
possibile attivare unestensione SOAP lato client tramite gli attributi personalizzati, esattamente
come accade per quelle lato server, a eccezione del fatto che tali attributi vengono applicati
ai metodi della classe proxy. Unestensione SOAP lato client passa attraverso gli stessi quattro
stati visti per quelle lato server, ma segue un ordine differente: BeforeSerialize e AfterSerialize
quando il messaggio SOAP viene inviato al Web service; BeforeDeserialize e AfterDeserialize
quando lXML risultante viene ricevuto dal Web service (Figura 29-9).

Web Service Extensions (WSE)


La storia dei Web service non si conclude con le tecniche illustrate in questo capitolo. Invece, ho il
sospetto che si tratti solo dellinizio di una saga che terr occupati gli sviluppatori per molti anni
ancora.Se si molto interessati ai Web service, si pu visitare http://www.msdn.microsoft.com/
webservices e scaricare la versione pi recente della libreria Web Services Enhancement (WSE),
che implementa delle funzionalit notevoli come lautenticazione, la crittograa e gli allegati
binari. Al momento in cui scrivo, disponibile la versione Technology Preview di WSE 2.0, che
aggiunge molte altre caratteristiche utili, compreso la sicurezza role-based e le transazioni. Queste
caratteristiche vengono implementate come estensioni SOAP, pertanto si pu sfruttare ci che si
appreso in questo capitolo.Quando ho iniziato a scrivere la seconda edizione di questo libro, ero
determinato ad inserire un capitolo su WSE, ma la velocit con cui la tecnologia dei Web service
si sta evolvendo avrebbe reso le informazioni obsolete dopo pochi mesi (o addirittura settimane).
Invece di inseguire questa nuova ed eccitante tecnologia, ho deciso di dedicare i nuovi capitoli
di questa edizione ad altre parti pi stabili del .NET Framework e che non subiranno notevoli
modiche a breve, ad iniziare da PInvoke e COM Interop.

Potrebbero piacerti anche