好的,我仔细调查了一下,并试图在我的这一侧复制此问题。我能够复制该问题并找到解决方法。然而,我不确定这是否适用于您的情况,因为它取决于与负载均衡器管理团队进行接口操作。以下是我的发现。
在查看http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html时,您会注意到HTTP状态代码302和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 Other
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 Found:这是业界实践与标准相矛盾的一个例子。HTTP/1.0 规范(RFC 1945)要求客户端执行临时重定向(最初的描述短语为“暂时移动”),但流行的浏览器使用 302 来实现 303 See Other 的功能。因此,HTTP/1.1 添加了状态码 303 和 307 来区分这两种行为。然而,一些 Web 应用和框架使用 302 状态码似乎就像是 303 状态码。
303 See Other (自 HTTP/1.1 开始):响应请求可以使用 GET 方法在另一个 URI 下找到。如果响应是针对 POST(或 PUT/DELETE)请求的,则应该假定服务器已经收到数据并且应该使用单独的 GET 消息发出重定向。这是正常的客户端/服务器交互中的基本流程。
307 Temporary Redirect (自 HTTP/1.1 开始):在这种情况下,请求应该使用另一个 URI 重复发送;但是,将来的请求仍应该使用原始 URI。与历史上如何实现 302 不同,重新发出原始请求时不允许更改请求方法。例如,应该使用另一个 POST 请求重复 POST 请求。
因此,根据这个解释,我们能够解释 WCF 调用的行为,该调用在 302 重定向时发送了一个没有 s:Envelope 的 GET 请求。这将在客户端端失败。
最简单的修复方法是让服务器在响应中返回 307 临时重定向而不是 302 Found 状态码。这就需要您寻求负责负载均衡器上重定向规则的服务器团队的帮助。我在本地进行了测试,使用 Service Reference 消费服务的客户端代码无缝执行调用,即使使用 307 临时重定向。
实际上,您可以使用我在 Github 上上传的解决方案 Here 测试所有这些。我已更新这个项目来说明使用服务引用而不是生成的 wsdl 代理类来消费 asmx 服务。
但是,如果在您的环境中从 302 Found 到 307 Temporary Redirect 的更改不可行,则建议使用 Solution 1(无论响应状态码是 302 还是 307 都不应该有问题)或使用我的 original answer,通过直接访问配置文件中的正确 URL 来解决此问题。希望这可以帮到您!
Solution 1
如果您在生产环境中无法访问配置文件,或者您不想在配置文件中使用多个URL,那么可以使用以下方法。链接到包含示例解决方案的Github存储库
点击此处。
基本上,如果您注意到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
to
public partial class WebApiProxy : SoapHttpClientProtocolWithRedirect
现在按照以下方式调用DoLogin方法。
var apiClient = new WebApiProxy(GetServiceUrl())
//TODO: Add any required headers etc.
apiClient.DoLogin(username,password)
您会注意到,302重定向是由您的SoapHttpClientProtocolWithRedirect类中的代码平稳处理的。
另一个优点是这样做,您不必担心其他开发人员会刷新服务引用并丢失您手动生成的代理类所做的更改。希望这可以帮助您。
原始答案:
为什么不在配置文件中包含生产/本地服务的整个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)
{
return string.Empty;
}
}
那么您的配置文件可以在 部分中包含以下信息。
<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"/>
另外,我建议避免使用“+”运算符连接字符串。当只使用一次时,它不会对性能产生太大的影响,但如果在代码中多次使用这种连接方式,与使用 StringBuilder 相比,执行时间会有很大差异。请查看
http://msdn.microsoft.com/en-us/library/ms228504.aspx
以了解更多关于为什么使用 StringBuilder 可以提高性能的信息。