Perché WCF non riesce a chiamare un servizio SOAP quando viene rilevata una risposta 302?

Ho scritto un’applicazione che inizia con l’esecuzione di una chiamata WCF per l’accesso. Ho generato il codice cliente con un riferimento al servizio. Funziona bene per i clienti che hanno i loro servizi installati localmente nella loro rete. Tuttavia, esiste anche un ambiente saas, in cui questi stessi servizi sono controllati dai poteri aziendali. Nell’ambiente saas, sono stato informato che il login stava fallendo. Investigando usando Fiddler, ho scoperto che la chiamata di servizio per il login sta restituendo HTML, in particolare, la pagina web che elenca tutti i metodi disponibili da .asmx.

L’ambiente saas ha una piccola stranezza che potrebbe causare il problema qui, ma non so come verificare che questo sia il problema, né come risolverlo se è il problema. Il problema è che il server reindirizza (302) la chiamata.

Il codice cliente:

     client.Endpoint.Address = new EndpointAddress ("http: //" + settings.MyURL + "/myProduct/webservices/webapi.asmx");
     client.DoLogin (username, password);

I dati non elaborati inviati al server, prima del reindirizzamento, includono il tag s: Envelope XML. Si noti la s mancante: tag XML Envelope quando si invia al server reindirizzato:

     OTTIENI https://www.myurl.com/myProduct/webservices/webapi.asmx HTTP / 1.1
     Content-Type: text / xml;  charset = utf-8
     VsDebuggerCausalityData: uIDPo7TgjY1gCLFLu6UXF8SWAoEAAAAAQQHTAupeAkWz2p2l3jFASiUPHh + L / 1xNpTd0YqI2o + wACQAA
     SOAPAction: " http://Interfaces.myProduct.myCompany.com/DoLogin "
 Accept-Encoding: gzip, deflate
     Host: www.gotimeforce2.com
     Connessione: Keep-Alive 

Come faccio a far funzionare questa cosa sciocca?

Modifica: Vale la pena notare che sto usando WCF / svcutil.exe / riferimento di servizio piuttosto che il vecchio ASMX / wsdl.exe / riferimento web. Altrimenti, per i futuri lettori di questo argomento, la soluzione wsdl suggerita da Raj sarebbe stata un’ottima soluzione. Se stai vedendo questo problema e stai usando la tecnica wsdl, vedi la risposta eccellente di Raj.

Edit2: Dopo aver fatto un po ‘ di ricerche su WCF e 302, sembra che semplicemente non giochino bene insieme, né sembra esserci un modo semplice di dare il codice personalizzato API a WCF per gestire la situazione. Poiché non ho alcun controllo sul server, l’ho risucchiato e ho rigenerato la mia API come riferimento web e sto usando la soluzione di Raj.

Edit3: aggiornato il titolo per riflettere meglio la soluzione, ora che la causa del problema è compresa. Titolo originale: Perché WCF non include s: Busta su un reindirizzamento?

Ok, quindi ho fatto qualche ricerca su questo e ho cercato di replicare il problema dalla mia parte. Sono stato in grado di replicare il problema e trovare una soluzione. Tuttavia non sono sicuro di quanto questo si applichi nel tuo caso in quanto dipende dall’interfacciamento con il team del server che gestisce il bilanciamento del carico. Ecco i risultati.

Guardando http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html si nota il seguente addendum nella spiegazione per i codici di stato HTTP 302 e 303.

302 trovati

  Note: RFC 1945 and RFC 2068 specify that the client is not allowed to change the method on the redirected request. However, most existing user agent implementations treat 302 as if it were a 303 response, performing a GET on the Location field-value regardless of the original request method. The status codes 303 and 307 have been added for servers that wish to make unambiguously clear which kind of reaction is expected of the client. 

303 Vedi Altro

  Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303. 

Esaminando ulteriormente http://en.wikipedia.org/wiki/List_of_HTTP_status_codes si nota la seguente spiegazione per i codici di stato HTTP 302, 303 e 307.

302 Found: Questo è un esempio di pratica industriale in contraddizione con lo standard. La specifica HTTP / 1.0 (RFC 1945) richiedeva al client di eseguire un reindirizzamento temporaneo (la frase descrittiva originale era “Spostata temporaneamente”), ma i browser più diffusi implementavano 302 con la funzionalità di un 303 Vedere Altro. Pertanto, HTTP / 1.1 ha aggiunto i codici di stato 303 e 307 per distinguere tra i due comportamenti. Tuttavia, alcune applicazioni Web e framework utilizzano il codice di stato 302 come se fosse il 303.

303 Vedi Altro (da HTTP / 1.1): la risposta alla richiesta può essere trovata sotto un altro URI usando un metodo GET. Quando viene ricevuto in risposta a un POST (o PUT / DELETE), si deve presumere che il server abbia ricevuto i dati e che il reindirizzamento debba essere emesso con un messaggio GET separato. Ecco il stream di base in una normale interazione client / server

307 Reindirizzamento temporaneo (da HTTP / 1.1): in questo caso, la richiesta deve essere ripetuta con un altro URI; tuttavia, le richieste future dovrebbero ancora utilizzare l’URI originale. In contrasto con il modo in cui la versione 302 è stata implementata storicamente, non è ansible modificare il metodo di richiesta durante il rilascio della richiesta originale. Ad esempio, una richiesta POST deve essere ripetuta utilizzando un’altra richiesta POST.

Quindi, in base a ciò, siamo in grado di spiegare il comportamento della chiamata WCF che invia una richiesta GET senza s: Envelope sul reindirizzamento 302. Ciò fallirà indubbiamente sul lato client.

Il modo più semplice per risolvere questo problema è far sì che il server restituisca 307 Temporary Redirect invece di 302 Found status code nella risposta. È qui che è necessario l’aiuto del Team di server che gestisce le regole di reindirizzamento sul bilanciamento del carico. L’ho verificato localmente e il codice client che utilizza il servizio con il riferimento di servizio esegue senza interruzioni la chiamata anche con il 307 Redirect temporaneo.

In effetti puoi testare tutto questo con la soluzione che ho caricato su Github Here . Ho aggiornato questo per illustrare l’utilizzo di un riferimento al servizio invece di una class proxy generata da wsdl per consumare il servizio asmx.

Tuttavia, se la modifica da 302 Trovato a 307 Ripristino temporaneo non è fattibile nel proprio ambiente, suggerirei di utilizzare la Soluzione 1 (che non dovrebbe avere problemi se si tratta di un codice di stato 302 o 307 nella risposta) o di utilizzare il mio risposta originale che risolverebbe questo accedendo direttamente al servizio all’URL corretto in base all’impostazione nel file di configurazione. Spero che questo ti aiuti!

Soluzione 1

Se non si ha accesso ai file di configurazione in produzione o se semplicemente non si desidera utilizzare gli URL multipli nel file di configurazione, è ansible utilizzare questo approccio seguente. Collegamento al repository Github contenente la soluzione di esempio Fare clic qui

In sostanza, se si nota il file generato automaticamente da wsdl.exe, si noterà che la class del proxy di servizio deriva da System.Web.Services.Protocols.SoapHttpClientProtocol . Questa class ha un metodo protetto System.Net.WebRequest GetWebRequest(Uri uri) che puoi sovrascrivere. Qui puoi aggiungere un metodo per verificare se un reindirizzamento temporaneo 302 è il risultato del metodo HttpWebRequest.GetResponse() . In tal caso, è ansible impostare l’Url sul nuovo Url restituito nell’intestazione Location della risposta come segue.

this.Url = new Uri(uri, response.Headers["Location"]).ToString();

Quindi creare una class chiamata SoapHttpClientProtocolWithRedirect come segue.

 public class SoapHttpClientProtocolWithRedirect : System.Web.Services.Protocols.SoapHttpClientProtocol { protected override System.Net.WebRequest GetWebRequest(Uri uri) { if (!_redirectFixed) { FixRedirect(new Uri(this.Url)); _redirectFixed = true; return base.GetWebRequest(new Uri(this.Url)); } return base.GetWebRequest(uri); } private bool _redirectFixed = false; private void FixRedirect(Uri uri) { var request = (HttpWebRequest)WebRequest.Create(uri); request.CookieContainer = new CookieContainer(); request.AllowAutoRedirect = false; var response = (HttpWebResponse)request.GetResponse(); switch (response.StatusCode) { case HttpStatusCode.Redirect: case HttpStatusCode.TemporaryRedirect: case HttpStatusCode.MovedPermanently: this.Url = new Uri(uri, response.Headers["Location"]).ToString(); break; } } } 

Ora arriva la parte che illustra il vantaggio dell’utilizzo di una class proxy generata manualmente usando wsdl.exe invece di un riferimento al servizio. Nella class proxy creata manualmente. modificare la dichiarazione della class da

 public partial class WebApiProxy : System.Web.Services.Protocols.SoapHttpClientProtocol 

a

 public partial class WebApiProxy : SoapHttpClientProtocolWithRedirect 

Ora richiama il metodo DoLogin come segue.

 var apiClient = new WebApiProxy(GetServiceUrl()); //TODO: Add any required headers etc. apiClient.DoLogin(username,password); 

Si noterà che il reindirizzamento 302 viene gestito senza problemi dal codice nella class SoapHttpClientProtocolWithRedirect.

Un altro vantaggio è che, facendo ciò, non dovrai temere che altri sviluppatori aggiorneranno il riferimento al servizio e perderanno le modifiche apportate alla class del proxy dopo averlo generato manualmente. Spero che questo ti aiuti.

Risposta originale

Perché non includi l’intero URL per produzione / servizio locale nel file di configurazione? In questo modo puoi avviare la chiamata con l’url appropriato nella posizione appropriata.

Inoltre, mi asterrò dall’usare un riferimento di servizio in qualsiasi codice destinato alla produzione. Un modo per utilizzare il servizio asmx senza un riferimento al servizio sarebbe generare il file WebApiProxy.cs utilizzando lo strumento wsdl.exe. Ora puoi solo includere il file WebApiProxy.cs nel tuo progetto e creare un’istanza come mostrato di seguito.

 var apiClient = new WebApiProxy(GetServiceUrl()); //TODO: Add any required headers etc. apiClient.DoLogin(username,password); 

Ecco il metodo GetServiceUrl (). Utilizzare un repository di configurazione per disaccoppiare e migliorare ulteriormente la testabilità.

 private string GetServiceUrl() { try { return _configurationRepository.AppSettings[ _configurationRepository.AppSettings["WebApiInstanceToUse"]]; } catch (NullReferenceException ex) { //TODO: Log error return string.Empty; } } 

Quindi il tuo file di configurazione può contenere le seguenti informazioni nella sezione.

     

Inoltre mi asterrò dal concatenare le stringhe usando il sovraccarico +. Quando lo si fa una volta, non si presenta come un impatto troppo grande sulle prestazioni, ma se si hanno molte concatenazioni come questa in tutto il codice, si otterrebbe una grande differenza nei tempi di esecuzione rispetto all’uso di StringBuilder. Controllare http://msdn.microsoft.com/en-us/library/ms228504.aspx per ulteriori informazioni sul motivo per cui l’utilizzo di StringBuilder migliora le prestazioni.