Windows Web Services API : Step->By-Step per il client

Il metodo più semplice per provare la nuova libreria WWSAPI è quella di partire da uno scenario WWSAPI client che si integra con un servizio WCF. Passo successivo creeremo lo stesso servizio in C/C++ con WWSAPI e vedremo che lato client ci basterà cambiare l’url del Web Service permettendoci di invocare indistintamente quello sviluppato in WCF e in WWSAPI.

Per crearsi l’ambiente dovremo scaricare il seguente software :

Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1 : BETA

Beta of Windows Web Services API for Windows XP, Vista, Server 2003 and Server 2008 (solo se non si sviluppa su WIndows 7 o Windows Server 2008 R2)

 

Web Service in WCF

Come prima cosa costruiamoci un servizio WCF (tipo di progetto VS2008 : WCF Service Application) che implementa una semplice interfaccia (ovviamente lo si può provare anche con un ASMX) :

 

namespace CalculatorService
{
[ServiceContract(Namespace = "
https://MarioFontanaPublicDemos.com/CalculatorService/")]
    public interface ICalculator
{

        [OperationContract]
long Add(int a, int b);

    }
}

 

Assicurandoci di impostare il binding su basicHttpBindings. Dovremo avere un web.config simile a questo:

<system.serviceModel>
<services>
<service behaviorConfiguration="CalculatorService.CalculatorServiceBehavior" name="CalculatorServiceServiceConfiguration">
<endpoint address="" bindingNamespace="
https://MarioFontanaPublicDemos.com/CalculatorService/Bindings" binding="basicHttpBinding" bindingConfiguration="" contract="CalculatorService.ICalculator">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorService.CalculatorServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>

 

A questo punto il nostro servizio dovrebbe avere un WSDL di questo tipo :

 

image

Prima di abbandonare il nostro servizio ricordiamoci di rendere fissa la porta su localhost. Nel caso di esempio : 62881 (quindi avremo : l’endpoint su : https://localhost:62881/CalculatorService.svc)

Client C/C++

Per creare il client C/C++ che consuma il nostro servizio WCF dobbiamo fare i seguenti passi :

 

image

  1. Ottenere i metadati dal Web Service tramite l’utility svcutil.exe (Microsoft Service Model Metadata Tool)
  2. Trasformare i metadati in dichiarazioni C tramite una seconda utility a riga di comando : wsutil.exe (Windows Web Services Tool) Attenzione : controllate che sia la versione 1.0079. in alcuni casi ho riscontrato che la versione 1.0069 che non funziona !!!
  3. Step succesivo scrivere il codice client.

 

Vediamo passo passo come fare : 

  1. Creiamo un nuovo progetto di tipo Win32 Console Application (può andare anche un qualsiasi progetto con interfaccia grafica, ma sapete io sono un nostalgico della cara vecchia console :-) e premiamo pure su finish perchè non ci interessa nessuna customizzazione.

  2. Andiamo nella configurazione del progetto e selezioniamo All Configuration in alto a sinistra nella finestra di Property Page.

    1. Selezioniamo Configuration Properties->C/C++ –> Precompiled Headers e alla voce Create/Use Precompiled Header impostiamo : Not Using Precompiled Headers.
    2. Selezioniamo Configuration Properties->Linker->Input e alla voce Additional Dependencies scriviamo WebServices.lib.
  3. Premiamo OK alla finestra di configurazione.

  4. Apriamo una console amministrativa e andiamo nella directory del nostro client. Qui possiamo creare una directory ad esempio : WSMetadata.

  5. Entriamo nella directory WSMetadata e lanciamo la nuova versione dell’utility a riga di comando svcutil.exe /t:metadata <indirizzo del nostro servizio WCF> quindi sarà ad esempio :

    svcutil.exe /t:metadata https://localhost:62881/CalculatorService.svc.

    In questo modo abbiamo scaricato i metadati dal servizio, ovvero il WSDL e gli xsd, e dovremmo avere un file WSDL e uno o più file XSD. Nel nostro caso avremo

    mariofontanapublicdemos.com.CalculatorService.Bindings.wsdl
    mariofontanapublicdemos.com.CalculatorService.wsdl
    mariofontanapublicdemos.com.CalculatorService.xsd
    schemas.microsoft.com.2003.10.Serialization.xsd

  6. A questo punto lanciamo un’altra utility a riga di comando wsutil.exe *.* (assicurarsi che non ci siano altri file oltre quelli creati nel punto precedente perchè altrimenti viene ovviamente generato un errore). Con questa operazione abbiamo creato per ogni file presente nella directory un equivalente .c e .h con la descrizione dello schema, del wsdl e delle policy che andermo ad includere nel nostro progetto. Nel nostro caso avremo quindi 12 files :

    mariofontanapublicdemos.com.CalculatorService.Bindings.wsdl
    mariofontanapublicdemos.com.CalculatorService.Bindings.wsdl.c
    mariofontanapublicdemos.com.CalculatorService.Bindings.wsdl.h
    mariofontanapublicdemos.com.CalculatorService.wsdl
    mariofontanapublicdemos.com.CalculatorService.wsdl.c
    mariofontanapublicdemos.com.CalculatorService.wsdl.h
    mariofontanapublicdemos.com.CalculatorService.xsd
    mariofontanapublicdemos.com.CalculatorService.xsd.c
    mariofontanapublicdemos.com.CalculatorService.xsd.h
    schemas.microsoft.com.2003.10.Serialization.xsd
    schemas.microsoft.com.2003.10.Serialization.xsd.c
    schemas.microsoft.com.2003.10.Serialization.xsd.h

  7. Aggiungiamo al progetto i files .h e .c appena creati nella directory WSMetadata.

  8. Aggiungiamo l’include ai file .h inseriti nel progetto e a <webservices.h> :

    #include <webservices.h>
    #include "..\\wsmetadata\\mariofontanapublicdemos.com.CalculatorService.Bindings.wsdl.h"
    #include "..\\wsmetadata\\mariofontanapublicdemos.com.CalculatorService.wsdl.h"
    #include "..\\wsmetadata\\mariofontanapublicdemos.com.CalculatorService.xsd.h"
    #include "..\\wsmetadata\\schemas.microsoft.com.2003.10.Serialization.xsd.h"

  9. Primo momento della verità : compilatina…e dovrebbe andare tutto OK

  10. Copiamo il codice riportato sotto e poi possiamo compilare nuovamente fiduciosi :-)

 

#include <iostream>
#include <conio.h>

WCHAR* WsGetErrorDescription(HRESULT hr);

//#define _WCF

int _tmain(int argc, _TCHAR* argv[])
{
    HRESULT hr;
    WS_ERROR* error;
    hr = WsCreateError(NULL,0, &error);
    if (FAILED(hr)) return -1;

    WS_HEAP* heap;
    hr= WsCreateHeap(1024,0,NULL,0,&heap, error);
    if(FAILED(hr))
    {
        WsFreeError(error);
        return -1;
    }

    WS_SERVICE_PROXY* serviceProxy;
    WS_HTTP_BINDING_TEMPLATE templateValue = {};

    hr = BasicHttpBinding_ICalculator_CreateServiceProxy(
        &templateValue,
        NULL,
        0,
        &serviceProxy,
        error);

    if(FAILED(hr))
    {
        WsFreeHeap(heap);
        WsFreeError(error);
        return -1;
    }

    WS_ENDPOINT_ADDRESS address = {};

#ifdef _WCF
    WS_STRING Url = WS_STRING_VALUE(L"https://localhost:62881/CalculatorService.svc");
#else
    WS_STRING Url = WS_STRING_VALUE(L"https://localhost:8080/NativeCalculatorService");
#endif

    address.url = Url;
    hr = WsOpenServiceProxy(serviceProxy, &address, NULL, error);

    if (FAILED(hr))
    {
        WsFreeServiceProxy(serviceProxy);
        WsFreeHeap(heap);
        WsFreeError(error);
        return -1;
    }
    int a = 1;
    int b = 2;
    __int64 result=0;
    hr = BasicHttpBinding_ICalculator_Add(serviceProxy,a,b,&result,heap,NULL,NULL,NULL,error);

    if (SUCCEEDED(hr))
    {
        wprintf(L"Invocazione del servizio Calculator : %d + %d = %X \n", a,b,result);

    }else
    {
        std::wcerr << static_cast<LPCTSTR>(WsGetErrorDescription(hr)) << std::endl;

        WS_STRING errordesc;
        hr = ::WsGetErrorString(error, 0, &errordesc);
        if (SUCCEEDED(hr))
        {
            std::wcerr << static_cast<LPCTSTR>(errordesc.chars) << std::endl;
        }
        else
        {
            wprintf(L"Ti sei ricordato di attivare il service host??");
        }
    }

    _getch();

    WsCloseServiceProxy(serviceProxy, NULL, error);

    WsFreeServiceProxy(serviceProxy);

    WsFreeHeap(heap);
    WsFreeError(error);
}

WCHAR* WsGetErrorDescription(HRESULT hr)
{
    switch(hr){

                    case WS_S_ASYNC:
                            return L" The function call is completing asynchronously";
                          break;
                    case WS_S_END:
                            return L" There are no more messages available on the channel";
                          break;
                    case WS_E_INVALID_FORMAT:
                            return L" The input data was not in the expected format or did not have the expected value";
                          break;
                    case WS_E_OBJECT_FAULTED:
                            return L" The operation could not be completed because the object is in a faulted state due to a previous error";
                          break;
                    case WS_E_NUMERIC_OVERFLOW:
                            return L" The operation could not be completed because it would lead to numeric overflow";
                          break;
                    case WS_E_INVALID_OPERATION:
                            return L" The operation is not allowed due to the current state of the object";
                          break;
                    case WS_E_OPERATION_ABORTED:
                            return L" The operation was aborted";
                          break;
                    case WS_E_ENDPOINT_ACCESS_DENIED:
                            return L" Access was denied by the remote endpoint";
                          break;
                    case WS_E_OPERATION_TIMED_OUT:
                            return L" The operation did not complete within the time allotted";
                          break;
                    case WS_E_OPERATION_ABANDONED:
                            return L" The operation was abandoned";
                          break;
                    case WS_E_QUOTA_EXCEEDED:
                            return L" A quota was exceeded";
                          break;
                    case WS_E_NO_TRANSLATION_AVAILABLE:
                            return L" The information was not available in the specified language";
                          break;
                    case WS_E_SECURITY_VERIFICATION_FAILURE:
                            return L" Security verification was not successful for the received data";
                          break;
                    case WS_E_ADDRESS_IN_USE:
                            return L" The address is already being used";
                          break;
                    case WS_E_ADDRESS_NOT_AVAILABLE:
                            return L" The address is not valid for this context";
                          break;
                    case WS_E_ENDPOINT_NOT_FOUND:
                            return L" The remote endpoint does not exist or could not be located";
                          break;
                    case WS_E_ENDPOINT_NOT_AVAILABLE:
                            return L" The remote endpoint is not currently in service at this location";
                          break;
                    case WS_E_ENDPOINT_FAILURE:
                            return L" The remote endpoint could not process the request";
                          break;
                    case WS_E_ENDPOINT_UNREACHABLE:
                            return L" The remote endpoint was not reachable";
                          break;
                    case WS_E_ENDPOINT_ACTION_NOT_SUPPORTED:
                            return L" The operation was not supported by the remote endpoint";
                          break;
                    case WS_E_ENDPOINT_TOO_BUSY:
                            return L" The remote endpoint is unable to process the request due to being overloaded";
                          break;
                    case WS_E_ENDPOINT_FAULT_RECEIVED:
                            return L" A message containing a fault was received from the remote endpoint";
                          break;
                    case WS_E_ENDPOINT_DISCONNECTED:
                            return L" The connection with the remote endpoint was terminated";
                          break;
                    case WS_E_PROXY_FAILURE:
                            return L" The HTTP proxy server could not process the request";
                          break;
                    case WS_E_PROXY_NOT_FOUND:
                            return L" The HTTP proxy server does not exist or could not be located";
                          break;
                    case WS_E_PROXY_ACCESS_DENIED:
                            return L" Access was denied by the HTTP proxy server";
                          break;
                    case WS_E_NOT_SUPPORTED:
                            return L" The requested feature is not available on this platform";
                          break;
                    case WS_E_PROXY_REQUIRES_BASIC_AUTH:
                            return L" The HTTP proxy server requires HTTP authentication scheme ";
                          break;
                    case WS_E_PROXY_REQUIRES_DIGEST_AUTH:
                            return L" The HTTP proxy server requires HTTP authentication scheme ";
                          break;
                    case WS_E_PROXY_REQUIRES_NTLM_AUTH:
                            return L" The HTTP proxy server requires HTTP authentication scheme ";
                          break;
                    case WS_E_PROXY_REQUIRES_NEGOTIATE_AUTH:
                            return L" The HTTP proxy server requires HTTP authentication scheme ";
                          break;
                    case WS_E_SERVER_REQUIRES_BASIC_AUTH:
                            return L" The remote endpoint requires HTTP authentication scheme ";
                          break;
                    case WS_E_SERVER_REQUIRES_DIGEST_AUTH:
                            return L" The remote endpoint requires HTTP authentication scheme ";
                          break;
                    case WS_E_SERVER_REQUIRES_NTLM_AUTH:
                            return L" The remote endpoint requires HTTP authentication scheme ";
                          break;
                    case WS_E_SERVER_REQUIRES_NEGOTIATE_AUTH:
                            return L" The remote endpoint requires HTTP authentication scheme ";
                          break;
                    case WS_E_INVALID_ENDPOINT_URL:
                            return L" The endpoint address URL is invalid";
                          break;

                }                   

}

Volendo schematizzare le macro funzioni del codice :

image

Lanciando prima il Web Service in WCF e poi il nostro clientino dovremmo avere la fatidica risposta della somma !!

Nel prossimo post creeremo lo stesso servizio con le Windows Web Services API per dimostrare la completa interoperabilità tra WCF e WWSAPI.

--Mario