- WebService: chiamare un metodo su un server da remoto -
 
COSA SERVE PER QUESTO TUTORIAL
Download | Chiedi sul FORUM | Glossario un server che supporti ASP .Net
Cosa sono, come utilizzare e come funzionano i WebService di .Net

IL WEBSERVICE
In cosa consiste e come realizzare un semplice WebService per effettuare query SQL da remoto.

In un precedente articolo abbiamo visto come effettuare la serializzazione XML di oggetti, e abbiamo accennato che questo poteva essere utile per far comunicare tra loro piattaforma diverse e non sulla stessa macchina. Oggi esploreremo un'altra funzionalità che si basa sulla serializzazione XML davvero interessante: i WebService. Un WebService non è nient'altro che una classe che si trova su un server ASP .Net (in un file con estensione ASMX) dotata di alcuni metodi che possono essere richiamati in maniera diretta da una macchina in remoto (il client). Per comprendere meglio facciamo un esempio molto pratico: alcuni servizi di hosting non permettono di accedere al DBMS da remoto, ovvero ad esempio nel caso di MySQL impediscono di connettersi sulla porta 3306 tramite client come SQLYog o Navicat. Proprio per questo, questi ultimi hanno studiato delle soluzioni di HTTP Tunneling consistenti in una pagina PHP verso la quale viene effettuata la richiesta e che restituisce, in un formato che non ci occuperemo ora di indagare, il risultato della query. Il WebService che svilupperemo in questo articolo mira proprio a questo, ma in una maniera più elegante e semplice: non dovremo infatti inventare alcun formato con cui restituire (e quindi poi reinterpretare dal lato client) il risultato della query in quanto sfrutteremo le funzionalità di .Net basate su SOAP.
In sostanza da una macchina in remoto rispetto al server potremo effettuare una chiamata del tipo:

dsMioDataSet = WebService.EseguiQuestaQuery("user", "pass", "SELECT * FROM utenti")

Iniziamo con lo scrivere una semplice funzione che data una stringa di connessione, la query SQL e una serie di parametri restituisce un DataSet con i risultati della query.


Public Function ExecuteQuery(ByVal ConnectionString As String, ByVal Command As String, _
    ByVal Parameters As OdbcParameter()) As DataSet
    
    Dim cnt As New OdbcConnection(ConnectionString)
    
    Dim adp As New OdbcDataAdapter(Command, cnt)
    adp.SelectCommand.Parameters.AddRange(Parameters)
    
    Dim dsResult As New DataSet
    adp.Fill(dsResult)
    
    Return dsResult
End Function

La funzione dovrebbe essere abbastanza autoesplicativa, non fa null'altro che ricevere una stringa di connessione, la query da eseguire ed un array di parametri (opzionalmente) e restituisce il DataSet di risultato al chiamante. Ora trasformiamo questo semplice metodo in un WebService, salvandolo in un file con estensione ASMX all'interno di una cartella del server (nel nostro caso webservice.asmx, nella root di IIS):


<%@ WebService Language="VB" Class="Querytor" %>

Imports System.Web.Services
Imports System.Data
Imports System.Data.odbc

Public Class Querytor
    <WebMethod()> _
    Public Function ExecuteQuery(ByVal ConnectionString As String, ByVal Command As String, _
        ByVal Parameters As OdbcParameter()) As DataSet
        [...]
    End Function
End Class

Vediamo in dettaglio le modifiche che abbiamo apportato:

  • abbiamo aggiunto una direttiva simile a quella che si usa per le normali pagine ASP .Net ma al posto di Page troviamo WebService, seguito dal linguaggio utilizzato (attributo Language) e dalla classe (attributo Class) che contiene il corpo del WebService, nel nostro caso chiamata Querytor;
  • abbiamo importato alcuni namespace, tra i quali System.Web.Services per l'attributo WebMethod;
  • abbiamo creato la classe Querytor che, come detto, è il WebService stesso e contiene la funzione ExecuteQuery;
  • infine abbiamo aggiunto l'attributo WebMethod per la funzione ExecuteQuery il che serve ad indicare che si tratta di un metodo che può essere richiamato da remoto (è possibile aggiungere ed utilizzare quanti altri metodi senza questo attributo si desideri, senza che però questi possano essere richiamati da remoto);

Imponiamo immediatamente qualche restrizione che riguarda i WebMethod: essi non possono restituire o ricevere come parametri oggetti che non supportino la serializzazione XML (come i dizionari e molti altri), quindi in genere ci si dovrà limitare a oggetti dei tipi primitivi (Integer e derivati, String, Single, Double e così via), classi, strutture e array formati da tipi primitivi o anche più complessi purché dispongano della possibilità di essere serializzate in XML (come ad esempio il DataSet, di cui facciamo uso nella nostra funzione).

L'APPLICAZIONE LATO CLIENT
Una semplice applicazione console che richiama il webservice dopo aver generato il codice necessario con il tool wsdl.

A questo punto abbiamo preparato il nostro WebService e possiamo provare ad utilizzarlo dal lato client. Per poter utilizzare un WebService è necessario generare del codice apposito che farà da wrapper verso il WebService. Nel caso compiliate il vostro codice manualmente da linea di comando o comunque non vi serviate di Visual Studio, per generare il codice necessario utilizzare il tool wsdl (che potrete trovare nella SDK) da linea di comando specificando l'URL in cui si trova il WebServices e il linguaggio in cui generare il codice. Se il WebService si trova in "http://localhost/webservice.asmx" e si vuole generare codice per Visual Basic sotto il namespace MioWebService utilizzare il seguente comando:

wsdl http://localhost/webservice.asmx /language:VB /namespace:MioWebService

Per ulteriori informazioni digitare "wsdl -?". Se invece si utilizza Visual Studio basta aggiungere una Web Reference al progetto. L'esempio seguente considera una semplice applicazione console che esegue la query invocando il WebService e stampa il DataSet in formato XML:


Module RemoteQuery

    Sub Main()
        Dim qry As New MioWebService.Querytor
        qry.Url = "http://localhost/webservice.asmx"

        Dim dsResults As DataSet
        dsResults = qry.ExecuteQuery("stringa di connessione", "SELECT * FROM utenti", Nothing)

        Console.Write(dsResults.GetXml)
        Console.ReadLine()
    End Sub

End Module

Come si può vedere abbiamo semplicemente creato un'istanza della classe Querytor (che è in realtà stata generata tramite wsdl) e su di essa richiamiamo il metodo ExecuteQuery come se fosse una funzione qualunque. Si noti anche che è stato impostata la proprietà Url di Querytor, che lato server non è stata definita, si tratta infatti di un campo generato sempre da wsdl (o comunque Visual Studio), che permette di impostare a run-time il percorso in cui si trova il WebService, cosicché con la stessa classe sia possibile interagire con più WebService identici ma su server differenti. In questo caso l'URL corrisponde a quello originale (ovvero quello specificato come parametro a wsdl o nella finestra di Visual Studio), dunque era del tutto superfluo specificarlo.

COME FUNZIONA SOTTO
Cosa succede effettivamente quando si chiama un WebService.

La chiamata a ExecuteQuery fa sì che il framework effettui una richiesta HTTP POST verso l'URL specificato simile a questa:


POST /webservice.asmx HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.1433)
VsDebuggerCausalityData: uIDPo4ZGNMgGl1VKlaRnOSdNPhcAAAAA4979HqMnrU2rrHkYxak3vogGy8D56FVDlRoZZLaeB7sACAAA
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://tempuri.org/ExecuteQuery"
Host: localhost:81
Content-Length: 401
Expect: 100-continue
Connection: Keep-Alive

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <soap:Body>
        <ExecuteQuery xmlns="http://tempuri.org/">
            <ConnectionString>stringa di connessione</ConnectionString>
            <Command>SELECT * FROM utenti</Command>
        </ExecuteQuery>
    </soap:Body>
</soap:Envelope>

È una semplice richiesta HTTP che invia del codice XML in formato SOAP e nel quale, senza dilungarci, possiamo riconoscere un elemento ExecuteQuery che contiene altri due elementi che racchiudono rispettivamente i valori dei parametri ConnectionString e Command serializzati (il parametro Parameters non è presente in quanto è stato passato come Nothing).
Vediamo ora la risposta del server:


HTTP/1.1 200 OK
Server: Microsoft-IIS/5.1
Date: Sun, 30 Nov 2008 16:21:33 GMT
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Content-Length: 1898

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <soap:Body>
        <ExecuteQueryResponse xmlns="http://tempuri.org/">
            <ExecuteQueryResult>
                <xs:schema id="NewDataSet" xmlns=""
                    xmlns:xs="http://www.w3.org/2001/XMLSchema"
                    xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">

                    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
                        <xs:complexType>
                            <xs:choice minOccurs="0" maxOccurs="unbounded">
                                <xs:element name="Table1">
                                    <xs:complexType>
                                        <xs:sequence>
                                            <xs:element name="Column1" type="xs:string" minOccurs="0" />
                                            <xs:element name="Column2" type="xs:string" minOccurs="0" />
                                            <xs:element name="Column3" type="xs:string" minOccurs="0" />
                                        </xs:sequence>
                                    </xs:complexType>
                                </xs:element>
                            </xs:choice>
                        </xs:complexType>
                    </xs:element>
                </xs:schema>
                <diffgr:diffgram 
                    xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
                    xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">

                    <NewDataSet xmlns="">
                        <Table1 diffgr:id="Table11" msdata:rowOrder="0" diffgr:hasChanges="inserted">
                            <Column1>sdf</Column1>
                            <Column2>134</Column2>
                            <Column3>dd</Column3>
                        </Table1>
                        <Table1 diffgr:id="Table12" msdata:rowOrder="1" diffgr:hasChanges="inserted">
                            <Column1>sdasdf</Column1>
                            <Column2>1234</Column2>
                            <Column3>ddddd</Column3>
                        </Table1>
                    </NewDataSet>
                </diffgr:diffgram>
            </ExecuteQueryResult>
        </ExecuteQueryResponse>
    </soap:Body>
</soap:Envelope>

In questo caso invece possiamo individuare un elemento ExecuteQueryResult contenete i dati restituiti dalla funzione serializzati (un DataSet) suddiviso in una parte contenente la descrizione della struttura dei dati (elemento xs:schema e figli) e una parte con i dati veri e propri (elemento NewDataSet e figli). Rimandiamo una trattazione più dettagliata di questo responso ad un articolo a venire.

 

<< INDIETRO by VeNoM00