如何从WebClient获取状态码?

98

我正在使用WebClient类将一些数据提交到Web表单。我想获取表单提交的响应状态码。到目前为止,我已经发现了如何在出现异常时获取状态码。

Catch wex As WebException
        If TypeOf wex.Response Is HttpWebResponse Then
          msgbox(DirectCast(wex.Response, HttpWebResponse).StatusCode)
            End If

然而,如果表单成功提交且没有抛出异常,则我将不知道状态码(200、301、302等)。

是否有一种方法可以在没有抛出异常的情况下获取状态码?

PS:我不想使用httpwebrequest / httpwebresponse。

10个回答

95
您可以检查错误是否属于WebException类型,然后检查响应代码。
if (e.Error.GetType().Name == "WebException")
{
   WebException we = (WebException)e.Error;
   HttpWebResponse response = (System.Net.HttpWebResponse)we.Response;
   if (response.StatusCode==HttpStatusCode.NotFound)
      System.Diagnostics.Debug.WriteLine("Not found!");
}
或者
try
{
    // send request
}
catch (WebException e)
{
    // check e.Status as above etc..
}

非常感谢您的回答,它指引了我正确的获取响应头的方法——从WebException中获取,而不是从WebClient.ResponseHeaders中获取。 - Hong
1
是的,最好的方法实际上是在try catch块中读取响应数据并捕获WebException。 - Henrik Hartz
通常情况下,您应该先捕获那种类型的异常,然后再捕获更一般的异常。而不是使用if(getType)。这可能值得编辑一下? - MondayPaper
2
我这里少了些什么。'System.Exception'和'System.Net.Exception'都没有包含'Error'的定义。 - Greg Woods
15
如果调用成功(即返回2xx或3xx),则不会有例外。原帖作者正在寻找3xx,我正在寻找204,其他人正在寻找201。这并没有回答所提出的问题。 - Simon Brooke
6
原帖写道:“当没有抛出异常时,有没有一种方法可以获得状态码?”不确定为什么这个回答得到了这么多赞,现在也没必要再踩了。 - Frog Pr1nce

36

有一种使用反射的方法可以实现这个。它适用于.NET 4.0。它访问了一个私有字段,可能需要在其他版本的.NET上进行修改才能正常工作。

我不知道为什么微软没有通过属性公开这个字段。

private static int GetStatusCode(WebClient client, out string statusDescription)
{
    FieldInfo responseField = client.GetType().GetField("m_WebResponse", BindingFlags.Instance | BindingFlags.NonPublic);

    if (responseField != null)
    {
        HttpWebResponse response = responseField.GetValue(client) as HttpWebResponse;

        if (response != null)
        {
            statusDescription = response.StatusDescription;
            return (int)response.StatusCode;
        }
    }

    statusDescription = null;
    return 0;
}

2
就此而言,在Windows Phone上是不可能的,即使通过反射也无法访问私有成员。 - Brendan
请注意,BindingFlags需要添加 "using System.Reflection;"。 - dlchambers
不错,但有没有办法获取SubStatusCode?例如403.1或403.2? - Roni Tovi
响应对象具有SubStatusCode属性。https://msdn.microsoft.com/zh-cn/library/system.web.httpresponse.substatuscode.aspx - Dmitry S.

30
如果您使用的是 .Net 4.0(或更早)版本:
class BetterWebClient : WebClient
{
        private WebRequest _Request = null;

        protected override WebRequest GetWebRequest(Uri address)
        {
            this._Request = base.GetWebRequest(address);

            if (this._Request is HttpWebRequest)
            {
                ((HttpWebRequest)this._Request).AllowAutoRedirect = false;
            }

            return this._Request;
        } 

        public HttpStatusCode StatusCode()
        {
            HttpStatusCode result;

            if (this._Request == null)
            {
                throw (new InvalidOperationException("Unable to retrieve the status 
                       code, maybe you haven't made a request yet."));
            }

            HttpWebResponse response = base.GetWebResponse(this._Request) 
                                       as HttpWebResponse;

            if (response != null)
            {
                result = response.StatusCode;
            }
            else
            {
                throw (new InvalidOperationException("Unable to retrieve the status 
                       code, maybe you haven't made a request yet."));
            }

            return result;
        }
    }

如果你正在使用 .Net 4.5.X 或更新版本,请切换到 HttpClient: HttpClient
var response = await client.GetAsync("http://www.contoso.com/");
var statusCode = response.StatusCode;

不适用于Windows Phone - GetWebResponse()仅存在于两个参数的版本中。仍然+1。 - Seva Alekseyev
有趣的是它不起作用。很高兴你的答案解决了问题!(https://dev59.com/_nA65IYBdhLWcg3w5y_B#19959477) - Erik Philips
对我起作用了,在高版本答案中没有反应。(.NET 4.5 Windows 7 和 10 应用程序) - David Shields

24

我尝试了一下,响应头不包括状态码。

如果我没记错的话,WebClient 可以在单个方法调用中抽象出多个不同的请求(例如正确处理100继续响应、重定向等)。我怀疑如果不使用HttpWebRequestHttpWebResponse,可能无法获得明确的状态码。

我想,如果你不关心中间状态码,你可以安全地假设最终状态码在 2xx(成功)范围内,否则,调用将不成功。

遗憾的是,状态码并没有出现在 ResponseHeaders 字典中。


2
似乎唯一的方法是使用WebRequest/Response。 - julio
1
如果您明确要查找其他200系列消息(即201 CREATED-请参见:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html),则可能会出现问题。:-/ 即使跳过了“中间”的消息,如果可以明确地使用该消息,那将是很好的。 - Norman H
1
@NormanH,我不反对。当涉及状态码时,WebClient似乎有点泄漏抽象。干杯! - kbrimington

9

Erik的回答在Windows Phone上不能直接使用。下面的方法可以:

class WebClientEx : WebClient
{
    private WebResponse m_Resp = null;

    protected override WebResponse GetWebResponse(WebRequest Req, IAsyncResult ar)
    {
        try
        {
            this.m_Resp = base.GetWebResponse(request);
        }
        catch (WebException ex)
        {
            if (this.m_Resp == null)
                this.m_Resp = ex.Response;
        }
        return this.m_Resp;
    }

    public HttpStatusCode StatusCode
    {
        get
        {
            if (m_Resp != null && m_Resp is HttpWebResponse)
                return (m_Resp as HttpWebResponse).StatusCode;
            else
                return HttpStatusCode.OK;
        }
    }
}

至少在使用OpenReadAsync时会这样; 对于其他xxxAsync方法,强烈建议进行仔细的测试。框架在代码路径中的某个位置调用GetWebResponse; 所有需要做的就是捕获并缓存响应对象。
此片段中的回退代码为200,因为真正的HTTP错误-500、404等-都会被报告为异常。这个技巧的目的是捕获非错误代码,在我的特定情况下是304(未修改)。因此,备用方案假定,如果状态码在某种程度上不可用,至少它不是一个错误的状态码。

4
这是我用来扩展WebClient功能的代码。StatusCode和StatusDescription将始终包含最新的响应代码/描述。
                /// <summary>
                /// An expanded web client that allows certificate auth and 
                /// the retrieval of status' for successful requests
                /// </summary>
                public class WebClientCert : WebClient
                {
                    private X509Certificate2 _cert;
                    public WebClientCert(X509Certificate2 cert) : base() { _cert = cert; }
                    protected override WebRequest GetWebRequest(Uri address)
                    {
                        HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
                        if (_cert != null) { request.ClientCertificates.Add(_cert); }
                        return request;
                    }
                    protected override WebResponse GetWebResponse(WebRequest request)
                    {
                        WebResponse response = null;
                        response = base.GetWebResponse(request);
                        HttpWebResponse baseResponse = response as HttpWebResponse;
                        StatusCode = baseResponse.StatusCode;
                        StatusDescription = baseResponse.StatusDescription;
                        return response;
                    }
                    /// <summary>
                    /// The most recent response statusCode
                    /// </summary>
                    public HttpStatusCode StatusCode { get; set; }
                    /// <summary>
                    /// The most recent response statusDescription
                    /// </summary>
                    public string StatusDescription { get; set; }
                }

因此,您可以通过以下方式进行发帖并获取结果:
            byte[] response = null;
            using (WebClientCert client = new WebClientCert())
            {
                response = client.UploadValues(postUri, PostFields);
                HttpStatusCode code = client.StatusCode;
                string description = client.StatusDescription;
                //Use this information
            }

这对我非常有帮助,因为我正在寻找响应代码。不错的解决方案! - evilfish
请注意,与HttpClient不同的是,4xx和5xx响应会导致在“response = base.GetWebResponse(request);”行处抛出WebException异常。您可以从异常中提取状态和响应(如果存在)。 - mwardm
是的。您仍然需要像平常一样捕获异常。但是,如果没有异常,这将暴露出OP想要的内容。 - DFTR

3

您应该使用

if (e.Status == WebExceptionStatus.ProtocolError)
{
   HttpWebResponse response = (HttpWebResponse)ex.Response;             
   if (response.StatusCode == HttpStatusCode.NotFound)
      System.Diagnostics.Debug.WriteLine("Not found!");
}

4
这个为什么被投票顶上来了?原帖很明确地表示:但是如果表单提交成功并且没有抛出异常... - Kenneth K.

1

万一有其他人需要上述描述的黑客攻击的F#版本。

open System
open System.IO
open System.Net

type WebClientEx() =
     inherit WebClient ()
     [<DefaultValue>] val mutable m_Resp : WebResponse

     override x.GetWebResponse (req: WebRequest ) =
        x.m_Resp <- base.GetWebResponse(req)
        (req :?> HttpWebRequest).AllowAutoRedirect <- false;
        x.m_Resp

     override x.GetWebResponse (req: WebRequest , ar: IAsyncResult  ) =
        x.m_Resp <- base.GetWebResponse(req, ar)
        (req :?> HttpWebRequest).AllowAutoRedirect <- false;
        x.m_Resp

     member x.StatusCode with get() : HttpStatusCode = 
            if not (obj.ReferenceEquals (x.m_Resp, null)) && x.m_Resp.GetType() = typeof<HttpWebResponse> then
                (x.m_Resp :?> HttpWebResponse).StatusCode
            else
                HttpStatusCode.OK

let wc = new WebClientEx()
let st = wc.OpenRead("http://www.stackoverflow.com")
let sr = new StreamReader(st)
let res = sr.ReadToEnd()
wc.StatusCode
sr.Close()
st.Close()

-1
你应该能够使用 "client.ResponseHeaders [..]" 调用,参见此 link 获取响应内容的示例。

1
返回的响应头是服务器头,如server、date、pragma等,但没有状态码(200、301、404...)。 - julio
1
很抱歉,有点惊讶没有得到返回。 - Paul Hadfield

-1
您可以尝试使用此代码从WebException或OpenReadCompletedEventArgs.Error中获取HTTP状态码。它也适用于Silverlight,因为SL没有定义WebExceptionStatus.ProtocolError。
HttpStatusCode GetHttpStatusCode(System.Exception err)
{
    if (err is WebException)
    {
        WebException we = (WebException)err;
        if (we.Response is HttpWebResponse)
        {
            HttpWebResponse response = (HttpWebResponse)we.Response;
            return response.StatusCode;
        }
    }
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接