65.9K
CodeProject 正在变化。 阅读更多。
Home

基于事件的异步 WebRequest

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (8投票s)

2006年11月11日

CPOL

3分钟阅读

viewsIcon

49811

downloadIcon

704

一个示例项目,其中包含一个 BackgroundWebRequest 组件,该组件可用于 WinForms 项目中执行异步 WebRequests。该项目展示了如何使用 WebRequest 和 WebResponse 对象,以及如何实现 MSDN 上描述的基于事件的异步模式。

Sample Image - EventBasedAsyncWebRequest.png

引言

此代码示例旨在用作处理 System.Net 命名空间中的 WebRequestWebResponse 对象,以及如何在 MSDN 网站上定义的基于事件的异步模式的参考。

使用 WebRequest 和 WebResponse

使用 WebRequest 对象非常简单。以下代码片段将为 www.google.com 创建一个 WebRequest 并获取响应。

Dim request As WebRequest = WebRequest.Create("http://www.google.com")
Dim response As WebResponse = request.GetResponse()

处理 WebResponse 稍微有点棘手。首先,您必须从流中读取响应,然后,根据 WebResponse.ContentType,您需要弄清楚如何将其转换为所需的输出。以下函数将接受一个 WebResponse 并将结果读入 StringImageByte 数组。

    Public Shared Function ProcessResponseStream( _
        ByVal response As WebResponse, _
        Optional ByVal defaultCharset As String = "utf-8") _
        As Object

        Dim st As IO.Stream
        st = response.GetResponseStream()

        Dim mem As New IO.MemoryStream()
        Dim buffer(1024) As Byte
        Dim read As Integer = 0

        Try
            ' Read the response stream into memory
            Do
                read = st.Read(buffer, 0, buffer.Length)
                mem.Write(buffer, 0, read)
            Loop While read > 0
            st.Close()
            response.Close()

            ' Reset the memory position so we can read 
            ' from the stream.
            mem.Position = 0

            ' Parse the content type (ContentType is an 
            ' internal class).
            Dim contentType As ContentType
            contentType = _
                New ContentType(response.ContentType)

            Select Case contentType.Type
                Case "text"
                    ' we should be able to read any text 
                    ' content into a string (assuming we 
                    ' have the correct encoding type).
                    Dim result(CInt(mem.Length)) As Byte

                    mem.Read(result, 0, CInt(mem.Length))

                    ' We need to get the appropriate
                    ' charset in order to decode the 
                    ' byte array as a string.
                    ' This information can be sent
                    ' in the Content-Type. If it isn't
                    ' we need to use the default 
                    ' charset.
                    Dim charset As String
                    charset = contentType.GetValue( _
                                    "charset", _
                                    defaultCharset)

                    ' We have the charset, now get the 
                    ' Encoding object and decode the 
                    ' content into a string.
                    Dim enc As Encoding
                    enc = Encoding.GetEncoding(charset)
                    Return enc.GetString(result)
                Case "image"
                    ' We should be able to read most image 
                    ' types directly into an image. 
                    Return Image.FromStream(mem)
                Case Else
                    ' Let the caller figure out how to 
                    ' handle this content.
                    Dim result(CInt(mem.Length)) As Byte
                    mem.Read(result, 0, CInt(mem.Length))
                    Return result
            End Select
        Finally
            mem.Close()
        End Try
    End Function

如果 Web 资源需要身份验证,您可以使用 WebRequest.Credentials 属性提供它。 只需将其设置为 System.Net.NetworkCredential 对象即可。 这允许您将用户的姓名、密码和域名发送到服务器,以提供用户对 Web 内容的访问权限。

如果您想在会话之间存储用户的凭据,您需要确保数据安全。如果您想了解更多相关信息,请研究 .NET 中的 DataProtection API。 您可以在我的博客文章 在 .NET 中保护数据中阅读更多相关信息。

基于事件的异步模式

这与 BackgroundWorker 用于提供异步处理,并在调用线程上引发事件的模式相同。这是一种非常有用的模式,并且 .NET 线程库使其易于实现。 MSDN 文档比我能想到的任何东西都要好,所以我鼓励您阅读它(我不会在这里讨论任何细则)。

这种模式的关键是 System.ComponentModel.AsyncOperation 类。 这个类允许您进行跨线程调用。 基本上,您在主线程上实例化该类,将其传递给一个新线程,然后调用 AsyncOperation.Post(AddressOf MyMethod, MyArg)。 然后 AsyncOperation 将使用 Windows 消息传递在调用线程上调用您的方法(您需要查看文档以了解此方面的细则:))。 完成线程后,调用 AsyncOperation.PostOperationCompleted(AddressOf MyMethod, MyArg)

如果您有兴趣了解我是如何发现这种模式的,您可以阅读我的博客文章 .NET 线程的圣杯

BackgroundWebRequest 组件

NetLib 项目(包含在此文章的下载中)使用基于事件的异步模式来提供用于下载 Web 内容的多线程解决方案。 它可以被放到 WinForm 上,像 BackgroundWorker 组件一样使用。 只需设置几个属性,处理几个事件,然后调用 BackgroundWebRequest.GetResponseAsync 方法。 此方法将返回一个充当请求键的对象。 所有特定于特定请求的事件和方法都使用此对象来标识请求(它只是一个 System.Object)。

以下代码示例展示了示例项目如何使用请求键来处理 BackgroundWebRequest.GetRequestCompleted 事件(注意,SetStatus 方法只是将标签设置为请求的状态)。

    Private mRequestKey As Object
    Private Sub txtAddress_Validated( _
        ByVal sender As System.Object, _
        ByVal e As System.EventArgs) _
        Handles txtAddress.Validated

        ' make sure we cancel the last request
        wrcBrowser.CancelRequest(mRequestKey)

        If txtAddress.Text = "" Then
            mRequestKey = Nothing
            browser.DocumentText = ""
        Else
            ' GetResponseAsyc returns an object that is
            ' used to identify the specific web request.
            ' The value must be stored in order to 
            ' perform operations on it (such as cancelling)
            mRequestKey = wrcBrowser.GetResponseAsync( _
                                        txtAddress.Text)
        End If
    End Sub
    Private Sub wrcBrowser_GetRequestCompleted( _
        ByVal sender As System.Object, _
        ByVal e As GetRequestCompletedEventArgs) _
        Handles wrcBrowser.GetRequestCompleted

        ' we only care about the last request that we made
        If e.RequestKey IsNot mRequestKey Then Return
        ' This method simply displays the status in the 
        ' status bar.
        SetStatus(e.Status, e.Error)

        If e.Status = WebRequestStatus.Complete Then
            ' The request completed successfully, dump the 
            ' html into the web browser.
            browser.DocumentText = CStr(e.ResponseResults)
            lblAddress.Text = e.Uri
        End If
    End Sub

关于示例代码的几个注意事项。 该代码没有经过任何严格的测试。 在这一点上,它只是一个示例应用程序。 有许多不同的错误情况,我目前没有处理,这些情况应该是生产代码的处理范围(一些遗漏是有意的,另一些则不是,你必须猜猜哪些是哪些 :))。 使用代码的风险由您自己承担,如果您发现任何重大问题,请告诉我。

© . All rights reserved.