Why was WCF unable to call the SOAP service when a 302 response was found?

I wrote an application that starts by calling WCF to login. I created a client code with a link to the service. It is great for customers who have their services installed locally on their network. However, there is a Saasian environment where these same services are controlled by corporate authority. In the saas environment, I was informed that the login did not work. While researching the use of Fiddler, I found that calling the login service returns HTML, in particular on a web page that lists all the available methods from .asmx.

There is one small quirk in saas environment that can cause a problem here, but I don’t know how to verify that it is a problem, and how to solve it if it is a problem. The quirk is that the server redirects (302) the call.

Client code:

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

The source data sent to the server before the redirect includes the s: Envelope XML tag. Note the missing s: Envelope XML tag when sending to the redirected server:

  GET https://www.myurl.com/myProduct/webservices/webapi.asmx HTTP / 1.1 Content-Type: text / xml;  encoding = UTF-8 VsDebuggerCausalityData: uIDPo7TgjY1gCLFLu6UXF8SWAoEAAAAAQxHTAupeAkWz2p2l3jFASiUPHh + L / 1xNpTd0YqI2o + wACQAA SOAPerction.product: ": 
 Accept-Encoding: gzip, deflate Host: www.gotimeforce2.com Connection: Keep-Alive 

How do I get this stupid job to work?

Edit: It's worth noting that I'm using WCF / svcutil.exe / service-reference, not the older version of ASMX / wsdl.exe / web-reference. Otherwise, for future readers of this topic, the wsdl solution proposed by Raj would be a great solution. If you see this problem and use the wsdl technique, see Raj for a great answer.

Edit2: After doing some research on WCF and 302, it sounds like they just don't play well together, and there seems to be no easy way to provide WCF api custom code to handle the situation. Since I don't have control over the server, I pulled it in and re-created my api as a web link, and I use the Raj solution.

Edit3: Updated the title to better reflect the solution, now that the cause of the problem is clear. Original title: Why does WCF not include s: Envelope when redirecting?

+7
source share
1 answer

Ok, so I worked on this a bit and tried to reproduce the problem on my side. I was able to reproduce the problem and find a solution for it. However, I am not sure how applicable this is in your case, since it depends on the interaction with the server team that controls the load balancer. Here are the results.

Looking at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html , you notice the following addition in the explanation for HTTP status codes 302 and 303.

302 Found

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

  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. 

Next, by looking at http://en.wikipedia.org/wiki/List_of_HTTP_status_codes , you will see the following explanation for HTTP status codes 302, 303, and 307.

302 Found: This is an example of industry practice that is contrary to the standard. The HTTP / 1.0 specification (RFC 1945) required the client to perform a temporary redirect (the original descriptive phrase was "Moved Temporarily") , but popular browsers implemented 302 with 303 functionality. See the section "Other". Therefore, HTTP / 1.1 added status codes 303 and 307 to distinguish between the two behaviors. However, some web applications and frameworks use a status code of 302, as if it were 303.

303 See Other (with HTTP / 1.1): The response to the request can be found in another URI using the GET method. When received in response to a POST (or PUT / DELETE), it should be assumed that the server received the data, and the redirect should be issued in a separate GET message. Here is the main thread in normal Client / Server interaction

307 Temporary redirect (with HTTP / 1.1): In this case, the request must be repeated with a different URI; however, future requests should still use the original URI. In contrast to how historically 302 was implemented, the request method cannot be changed when the original request is re-issued. For example, a POST request should be repeated using another POST request.

Thus, according to this, we can explain the behavior of the WCF call, which sends a GET request without s: Envelope when redirecting 302. This will undoubtedly fail on the client side.

The easiest way to fix this is to return the server to a 307 temporary redirect instead of the status code 302 of the response found. Here you need the help of Server Team, which manages the forwarding rules on the load balancer. I tested it locally, and client code consuming a service with Service Reference can make a call even with a 307 temporary redirect without problems.

In fact, you can test all of this with the solution I uploaded to Github here . I updated this to illustrate using a service link instead of the proxy class created by wsdl to use the asmx service.

However, if a change from 302 found in 307 temporary redirects is not possible in your environment, then I would suggest using either Solution 1 (which should not have a problem, whether it is 302 or 307 in the answer) or using my original answer , which would allow this by directly contacting the service at the correct URL based on the settings in the configuration file. Hope this helps!

Solution 1

If you do not have access to the configuration files during production, or if you simply do not want to use multiple URLs in the configuration file, you can use this following approach. Link to Github repository containing sample solution Click here

Basically, if you notice the auto file created by wsdl.exe, you will notice that the service proxy class is obtained from System.Web.Services.Protocols.SoapHttpClientProtocol . This class has a protected System.Net.WebRequest GetWebRequest(Uri uri) method, which you can override. Here you can add a validation method to determine if temporary redirect 302 is the result of the HttpWebRequest.GetResponse() method. If so, you can set Url for the new Url returned in the Response Location header, as shown below.

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

So, create a class called SoapHttpClientProtocolWithRedirect as follows.

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

Now comes the part that illustrates the advantage of using a proxy class manually created with wsdl.exe instead of a service reference. In a manually created proxy class. change class declaration from

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

to

 public partial class WebApiProxy : SoapHttpClientProtocolWithRedirect 

Now call the DoLogin method as follows.

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

You will notice that 302 redirection is handled by code in your SoapHttpClientProtocolWithRedirect class.

Another advantage is that you do not have to fear that any other developer will update the service link and lose the changes you made to the proxy class, since you manually created it. Hope this helps.

Original answer

Why don't you just include the entire URL for the production / local service in the configuration file? This way you can initiate a call with the appropriate URL in the appropriate place.

In addition, I would refrain from using the service link in any code intended for production. One way to use the asmx service without a service reference would be to generate a WebApiProxy.cs file using the wsdl.exe tool. Now you can simply include the WebApiProxy.cs file in your project and create an instance, as shown below.

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

Here is the GetServiceUrl () method. Use the configuration repository to further delimit and improve validation.

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

Your configuration file may then contain the following information in this section.

 <add key="StagingWebApiInstance" value="http://mystagingserver/myProduct/webservices/webapi.asmx "/> <add key="ProductionWebApiInstance" value="https://www.myurl.com/myProduct/webservices/webapi.asmx"/> <!-- Identify which WebApi.asmx instance to Use--> <add key="WebApiInstanceToUse" value="ProductionWebApiInstance"/> 

I would also refrain from concatenating strings using + overload. When you do this once, it does not occur as a too strong impact on performance, but if there is a lot of concatenation in the code, it will lead to a big difference in runtime compared to using StringBuilder. Check out http://msdn.microsoft.com/en-us/library/ms228504.aspx for more information on why using StringBuilder improves performance.

+6
source

All Articles