salve a tutti,
sono un niubbio totale in WCF e web services, ed ora devo provvedere a scaricare dei movimenti alberghieri all'ufficio ISTAT locale utilizzando un loro nuovo web service SOAP 1.1
il dominio di test e' https://rerprod.turitweb.it/ws/checkinV1 e la relativa definizione https://rerprod.turitweb.it/ws/checkinV1?wsdl
userName = "dataentry2"
password = "dataentry"
la documentazione molto stringata che hanno fornito indica::
authentication method is HTTP Basic, con credenziali codificate con Base64 encoding, con header esemplificativo:
POST /ws/checkinV1 HTTP/1.1
Accept: text/xml, multipart/related
Authorization: Basic TXlVc2VybmFtZTpNeVBhc3N3b3Jk
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
User-Agent:JAX-WS RI 2.2.9-b130926.1035
Host: rerprod.turitweb.it
Connection: keep-alive
Content-Length: 985
<?xml version="1.0"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:inviaMovimentazione .........
ho definito un semplicissimo e ridotto modulo di test che dovrebbe inviare un campione (gia' corretto) di dati;
mi sono connesso al wsdl per far generare a Visual Stuzio il proxie locale ed ho scritto le mie 5 triviali righe di codice per connettermi con la basic authentication utilizzando BasicHttpBinding (SOAP 1.1), con security mode System.ServiceModel.BasicHttpSecurityMode.Transport, che (come indicato in https://msdn.microsoft.com/en-us/library/ff647503.aspx) assicura che le credenziali siano trasportate con Base64 encoding, e mi pare che le specifiche siano cosi' state soddisfatte;
il config generato da Visual Studio generated e' come segue
<?xml version="1.0" encoding="utf-8"?>
<configurationSnapshot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:schemas-microsoft-com:xml-wcfconfigurationsnapshot">
<behaviors />
<bindings>
<binding digest="System.ServiceModel.Configuration.BasicHttpBindingElement, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089:<?xml version="1.0" encoding="utf-16"?><Data name="GestioneCheckinV1ImplServiceSoapBinding"><security mode="Transport" /></Data>" bindingType="basicHttpBinding" name="GestioneCheckinV1ImplServiceSoapBinding" />
<binding digest="System.ServiceModel.Configuration.BasicHttpBindingElement, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089:<?xml version="1.0" encoding="utf-16"?><Data name="GestioneCheckinV1ImplServiceSoapBinding1" />" bindingType="basicHttpBinding" name="GestioneCheckinV1ImplServiceSoapBinding1" />
</bindings>
<endpoints>
<endpoint normalizedDigest="<?xml version="1.0" encoding="utf-16"?><Data address="https://rerprod.turitweb.it/ws/checkinV1" binding="basicHttpBinding" bindingConfiguration="GestioneCheckinV1ImplServiceSoapBinding" contract="ServiceReference1.GestioneCheckinV1Impl" name="GestioneCheckinV1ImplPort" />" digest="<?xml version="1.0" encoding="utf-16"?><Data address="https://rerprod.turitweb.it/ws/checkinV1" binding="basicHttpBinding" bindingConfiguration="GestioneCheckinV1ImplServiceSoapBinding" contract="ServiceReference1.GestioneCheckinV1Impl" name="GestioneCheckinV1ImplPort" />" contractName="ServiceReference1.GestioneCheckinV1Impl" name="GestioneCheckinV1ImplPort" />
</endpoints>
</configurationSnapshot>
ed il mio codice
namespace TestIstat
{
class Program
{
static void Main(string[] args)
{
//utilizzo di un web service presso la regione emilia romagna
//https://rerprod.turitweb.it/ws/checkinV1 - https://rerprod.turitweb.it/ws/checkinV1?wsdl
string userName = "dataentry2";
string Password = "dataentry";
string Domain = "http://rerprod.turitweb.it/ws/checkinV1";
int TimeOut = 10;
// minimal working data load
ServiceReference1.xmlImportDati importDati = LoadImportDati();
System.ServiceModel.EndpointAddress address = new System.ServiceModel.EndpointAddress(Domain);
//'BasicHttpBinding supports SOAP 1.1. WsHttpBinding is for 1.2
System.ServiceModel.BasicHttpBinding binding = new System.ServiceModel.BasicHttpBinding();
binding.OpenTimeout = TimeSpan.FromSeconds(TimeOut);
binding.CloseTimeout = TimeSpan.FromSeconds(TimeOut);
binding.SendTimeout = TimeSpan.FromSeconds(TimeOut);
binding.ReceiveTimeout = TimeSpan.FromSeconds(TimeOut);
binding.Security.Transport.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Basic;
binding.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
ServiceReference1.GestioneCheckinV1ImplClient serviceClient = new ServiceReference1.GestioneCheckinV1ImplClient(binding, address);
serviceClient.ClientCredentials.UserName.UserName = userName;
serviceClient.ClientCredentials.UserName.Password = Password;
try
{
serviceClient.Open();
List<ServiceReference1.risultatoGiorno> results = serviceClient.inviaMovimentazione(importDati).ToList();
foreach (ServiceReference1.risultatoGiorno result in results)
{
Console.WriteLine(result.ToString());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
if ((serviceClient != null))
serviceClient.Abort();
}
if ((serviceClient != null))
serviceClient.Close();
serviceClient = null;
}
private static ServiceReference1.xmlImportDati LoadImportDati() {
ServiceReference1.xmlImportDati importDati = new ServiceReference1.xmlImportDati();
importDati.codice = "B01205";
importDati.prodotto = "S0011";
List<ServiceReference1.movimentoBean> movList = new List<ServiceReference1.movimentoBean>();
ServiceReference1.movimentoBean mov = new ServiceReference1.movimentoBean();
mov.data = "20170210";
mov.struttura = new ServiceReference1.strutturaBean();
mov.struttura.apertura = "NO";
mov.struttura.cameredisponibili = "36";
mov.struttura.camereoccupate = "0";
mov.struttura.lettidisponibili = "57";
movList.Add(mov);
importDati.movimento = movList.ToArray();
return importDati;
}
}
}
ma purtroppo mi viene sollevata l'eccezione "The HTTP request was forbidden with client authentication scheme 'Basic'."
ho chiesto agli sviluppatori, e loro, dopo piu' di un mese, mi hanno fornito un "workarount", che personalmente NON capisco e che mi sembra un hack...
il loro codice diventa
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net;
using System.Net.Http.Headers;
using System.Web;
using System.IO;
using System.ServiceModel;
//using System.Net.Http;
using System.ServiceModel.Channels;
namespace TestIstat
{
class Program
{
static void Main(string[] args)
{
//utilizzo di un web service presso la regione emilia romagna
//https://rerprod.turitweb.it/ws/checkinV1 - https://rerprod.turitweb.it/ws/checkinV1?wsdl
// minimal working data load
ServiceReference1.xmlImportDati importDati = LoadImportDati();
ServiceReference1.GestioneCheckinV1ImplClient serviceClient = new ServiceReference1.GestioneCheckinV1ImplClient();
//Credenziali Autenticazione web service.
serviceClient.ClientCredentials.UserName.UserName = "dataentry2";
serviceClient.ClientCredentials.UserName.Password = "dataentry";
using (OperationContextScope scope = new OperationContextScope(serviceClient.InnerChannel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
//Inesrisco nell'header http l'autenticazione : Authorization : Basic Username:password(converrtiti in base 64)
httpRequestProperty.Headers[System.Net.HttpRequestHeader.Authorization] = "Basic " +
Convert.ToBase64String(Encoding.ASCII.GetBytes(serviceClient.ClientCredentials.UserName.UserName + ":" +
serviceClient.ClientCredentials.UserName.Password));
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
try
{
List<ServiceReference1.risultatoGiorno> results = serviceClient.inviaMovimentazione(importDati).ToList();
foreach (ServiceReference1.risultatoGiorno result in results)
{
Console.WriteLine(result.ToString());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
if ((serviceClient != null))
serviceClient.Abort();
}
if ((serviceClient != null))
serviceClient.Close();
serviceClient = null;
}
}
private static ServiceReference1.xmlImportDati LoadImportDati() {
ServiceReference1.xmlImportDati importDati = new ServiceReference1.xmlImportDati();
importDati.codice = "B01205";
importDati.prodotto = "S0011";
List<ServiceReference1.movimentoBean> movList = new List<ServiceReference1.movimentoBean>();
ServiceReference1.movimentoBean mov = new ServiceReference1.movimentoBean();
mov.data = "20170210";
mov.struttura = new ServiceReference1.strutturaBean();
mov.struttura.apertura = "NO";
mov.struttura.cameredisponibili = "36";
mov.struttura.camereoccupate = "0";
mov.struttura.lettidisponibili = "57";
movList.Add(mov);
importDati.movimento = movList.ToArray();
return importDati;
}
}
}
dove usano
OperationContextScope per effettuare l'injection dell'header con le credenziali utente invece di utilizzare un metodo a me piu' tradizionale
domanda: e' veramente necessario scriverlo cosi'? o e' un hack risolvibile in maniera piu' tradizionale?se cosi' non fosse ed andasse mantenuto quel codice, posso poi modificare il dominio da quello di test a quello di produzione a runtime con un
serviceClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(Domain);
e finire di definire anche il timeout come segue
serviceClient.ClientCredentials.UserName.UserName = userName;
serviceClient.ClientCredentials.UserName.Password = userPassword;
serviceClient.Endpoint.Binding.CloseTimeout = TimeSpan.FromSeconds(TimeOut);
serviceClient.Endpoint.Binding.OpenTimeout = TimeSpan.FromSeconds(TimeOut);
serviceClient.Endpoint.Binding.ReceiveTimeout = TimeSpan.FromSeconds(TimeOut);
serviceClient.Endpoint.Binding.SendTimeout = TimeSpan.FromSeconds(TimeOut);
?
tra l'altro, lo sviluppatore mi dice che il suo codice "dovrebbe" funzionare anche con il dominio https di produzione (non l'ha provato)
grazie a tutti in anticipo
--
Andrea Montanari