为什么WCF在遇到302响应时无法调用SOAP服务?

我编写了一个应用程序,它首先要进行WCF调用登录。 我使用服务引用生成了客户端代码。 它适用于将其服务本地安装到其网络的客户端。 然而,还有一个saas环境,这些相同的服务由公司权力控制。 在saas环境中,我被告知登录失败。 调查使用Fiddler,我发现登录服务调用返回HTML,特别是列出.asmx中所有可用方法的网页。

saas环境有一个小怪癖可能导致这里的问题,但我不知道如何validation这是问题,如果问题是如何解决它。 怪癖是服务器重定向(302)呼叫。

客户端代码:

     client.Endpoint.Address = new EndpointAddress(“http://”+ settings.MyURL +“/ myProduct /webservices / webapi.asmx”);
     client.DoLogin(用户名,密码);

在重定向之前发送到服务器的原始数据包括s:Envelope XML标记。 发送到重定向的服务器时,请注意缺少的s:Envelope XML标记:

    获取https://www.myurl.com/myProduct/webservices/webapi.asmx HTTP / 1.1
     Content-Type:text / xml; 字符集= utf-8的
     VsDebuggerCausalityData:uIDPo7TgjY1gCLFLu6UXF8SWAoEAAAAAQxHTAupeAkWz2p2l3jFASiUPHh + L / 1xNpTd0YqI2o + wACQAA
     SOAPAction:“ http://Interfaces.myProduct.myCompany.com/DoLogin ”
 Accept-Encoding:gzip,deflate
    主持人:www.gotimeforce2.com
    连接:保持活力 

我如何让这个愚蠢的事情发挥作用?

编辑:值得注意的是,我使用的是WCF / svcutil.exe / service-reference,而不是旧的ASMX / wsdl.exe / web-reference。 否则,对于本主题的未来读者,Raj建议的wsdl解决方案将是一个很好的解决方案。 如果你看到这个问题并使用wsdl技术,请参阅Raj的优秀答案。

编辑2:在对WCF和302进行一系列研究之后,听起来他们只是不能很好地一起玩,也没有一种简单的方法来提供WCF api自定义代码来处理这种情况。 因为我无法控制服务器,所以我已经把它搞砸了并重新生成了我的api作为web-reference并使用了Raj的解决方案。

编辑3:更新标题以更好地反映解决方案,现在可以理解问题的原因。 原标题:为什么WCF不包含s:重定向的信封?

好的,所以我做了一些挖掘,并尝试在我身边复制这个问题。 我能够复制问题并找到解决方案。 但是我不确定这在你的情况下会有多好,因为它依赖于与管理负载均衡器的服务器团队的接口。 以下是调查结果。

查看http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html您会在HTTP状态代码302和303的说明中注意到以下附录。

302发现

  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见其他

  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. 

进一步查看http://en.wikipedia.org/wiki/List_of_HTTP_status_codes您会注意到HTTP状态代码302,303和307的以下说明。

302发现:这是与标准相矛盾的行业惯例的一个例子。 HTTP / 1.0规范(RFC 1945)要求客户端执行临时重定向(原始描述短语是“暂时移动”), 但是流行的浏览器实现302具有303 See Other的function。 因此,HTTP / 1.1添加了状态代码303和307来区分这两种行为。 但是,某些Web应用程序和框架使用302状态代码,就好像它是303一样。

303参见其他(自HTTP / 1.1起):可以使用GET方法在另一个URI下找到对请求的响应。 当接收到POST(或PUT / DELETE)时,应该假定服务器已经接收到数据,并且应该使用单独的GET消息发出重定向。 这是正常的客户端/服务器交互中的基本流程

307临时重定向(自HTTP / 1.1起):在这种情况下,请求应该使用另一个URI重复; 但是,未来的请求仍应使用原始URI。 与历史上实现302的方式相反,在重新发出原始请求时不允许更改请求方法。 例如,应使用另一个POST请求重复POST请求。

因此,根据这一点,我们能够解释WCF调用的行为,该调用在302重定向上发送没有s:Envelope的GET请求。 这无疑会在客户端失败。

解决此问题的最简单方法是让服务器在响应中返回307临时重定向而不是302 Found状态代码。 您需要在服务器团队的帮助下管理负载均衡器上的重定向规则。 我在本地测试了这一点,使用服务引用消费服务的客户端代码即使使用307临时重定向也可以无缝地执行调用。

事实上,您可以使用我上传到Github Here的解决方案来测试这一切。 我已经更新了这个,以说明使用服务引用而不是使用wsdl生成的代理类来使用asmx服务。

但是,如果在您的环境中从302 Found到307临时重定向的更改不可行,那么我建议使用解决方案1 (无论是响应中的302或307状态代码也不应该有问题)或使用我的通过根据配置文件中的设置直接访问正确URL的服务来解决此问题的原始答案 。 希望这可以帮助!

解决方案1

如果您无法访问生产中的配置文件,或者您只是想在配置文件中使用多个URL,则可以使用以下方法。 链接到包含样本解决方案的Github repo 单击此处

基本上,如果您注意到wsdl.exe自动生成的文件,您会注意到服务代理类派生自System.Web.Services.Protocols.SoapHttpClientProtocol 。 这个类有一个可以覆盖的受保护方法System.Net.WebRequest GetWebRequest(Uri uri) 。 在这里,您可以添加一个方法来检查302临时重定向是否是HttpWebRequest.GetResponse()方法的结果。 如果是这样,您可以将Url设置为响应的Location标头中返回的新Url,如下所示。

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

因此,创建一个名为SoapHttpClientProtocolWithRedirect的类,如下所示。

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

现在出现的部分说明了使用wsdl.exe而不是服务引用手动生成的代理类的优势。 在手动创建的代理类中。 修改类声明

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

 public partial class WebApiProxy : SoapHttpClientProtocolWithRedirect 

现在调用DoLogin方法,如下所示。

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

您会注意到SoapHttpClientProtocolWithRedirect类中的代码可以平滑地处理302重定向。

另一个优点是,通过这样做,您不必担心其他开发人员将刷新服务引用并丢失您对代理类所做的更改,因为您已手动生成它。 希望这可以帮助。

原始答案

为什么不在配置文件中包含生产/本地服务的整个URL? 这样,您就可以在适当的位置使用相应的url发起呼叫。

另外,我会避免在任何用于生产的代码中使用服务引用。 在没有服务引用的情况下使用asmx服务的一种方法是使用wsdl.exe工具生成WebApiProxy.cs文件。 现在,您可以在项目中包含WebApiProxy.cs文件并实例化,如下所示。

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

这是GetServiceUrl()方法。 使用配置存储库进一步解耦并提高可测试性。

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

然后您的配置文件可以在该部分中包含以下信息。

     

另外,我会避免使用+重载连接字符串。 在执行一次时,它不会产生太多的性能影响,但如果在整个代码中有许多这样的连接,那么与使用StringBuilder相比,它会导致执行时间的巨大差异。 有关使用StringBuilder提高性能的原因的详细信息,请查看http://msdn.microsoft.com/en-us/library/ms228504.aspx