熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

Web Service中保持ASP.net的狀態

2013-11-13 10:12:02  來源: .NET編程 

  簡介

網絡程序開發者們遇到的最普遍的問題就是如何在無狀態的基於HTTP協議的交互中保持狀態信息有許多聰明的辦法可以解決HTTP協議的無狀態問題例如對每個請求重復發送應用程序數據包使用HTTP認證機制來將請求映射到特定的用戶使用Cookie來存儲一系列請求的狀態等在技術中提供了一個非常有效的方案來保持狀態該方案隱藏了所有高難度的具有挑戰性的工作的細節用戶只需簡單地使用SystemWebSessionStateHttpSessionState類同時你也可以像在程序的Web頁面(aspx)中那樣在Web Service的方法中使用這個類只有一點小小的不同

的Session對象概述

的Session狀態信息是通過兩個機制保持其一是使用Cookie當客戶端發送一個請求到服務器端時服務器將發回一個附加HTTP SetCookie頭的響應信息而Cookie的值就是以鍵/值對的形式保存在該信息裡邊在對同一服務器的所有的同步請求中客戶端在HTTP Cookie頭中發送Cookie鍵/值對然後服務器可以將並發的請求同初始的請求對應起來使用一個保存會話的ID的cookie來保持會話狀態該ID標識被用來為特定的用戶找到與其對應的HttpSessionState類的實例類HttpSessionState僅僅提供了一個通用的數據集你可以在其中保存你需要的任何信息

用來保持狀態的另外一種機制是無須使用Cookie一些浏覽器被用戶設置為禁止使用Cookie或者干脆就不支持Cookie提供了一種機制來解決這個問題它的主要原理是將一個請求重定向到一個包含狀態ID的URL當該請求被接受到時這個嵌在URL中的ID被截取下來服務器通過該ID找到合適的HttpSessionState類的實例這種方式在HTTP協議的使用GET方式的請求中工作的很好但是的XML Web Service代碼中會出現問題

必須指出的是有些時候把信息直接存儲在Cookie中要比存儲在Session中更好避免使用Session可以節省服務器資源而且你也無須考慮一些煩人的問題比如定位一個特定的Session對象Session對象因為請求的長時間的延遲而被移除或者在服務器上沒必要地保留直到過期然而如果你有一些包含你不希望與你提供的服務的使用者共享的執行信息或者有一些你不希望通過未加密的信道傳輸的私有數據或者你認為將這些數據插入HTTP協議頭中是不切實際的那麼你就應該使用中的HttpSessionState它將使你輕松解決這些問題HttpSessionState類返回一個索引鍵用以將一個特定的用戶映射到一個為該用戶保存信息的HttpSessionState類的實例總之無論是的HttpSessionState類還是HTTP的Cookie都可以在 Web Service中使用

 為什麼要在XML Web Service中使用基於HTTP的機制來實現狀態保持呢?

在SOAP請求中有許多方法來保持狀態一個切實可行的方法就是在SOAP頭中包含一些像ASP中的會話ID的信息然而問題在於你不得不) 仍然要自己編寫服務器端代碼並且 ) 確信你的客戶會像對待HTTP Cookie一樣對待你的包含會話ID的SOAP頭並且將它附加到每個請求中回傳給你當然有很多時候使用SOAP頭的方法會很方便但是也有很多時候還不如使用基於HTTP協議的方法

很容易在中使用Session來保持狀態信息HttpSessionState類為你封裝了存儲Session狀態的細節問題絕大多數的客戶端已經能夠明白他們必須返回服務器設置的cookie而且HttpSessionState類也支持在SOAP通信中常用的底層傳輸因此很明顯使用的Session機制會是滿足狀態控制要求的明智的選擇

 使服務器支持Session

在中對Web方法的狀態支持默認是關閉的你必須為每個要使用Session狀態的Web方法顯式地激活Session支持激活Session支持的方法是添加一個EnableSession選項到你的函數的WebMethod屬性中並且將其值設置為true下面的代碼演示了如何激活Session並且在方法中訪問Session狀態信息

[]

<WebMethod(EnableSession:=True)> _
Public Function IncrementSessionCounterX() As Integer
Dim counter As Integer
If ContextSession(Counter) Is Nothing Then
counter =
Else
counter = ContextSession(Counter) +
End If
ContextSession(Counter) = counter
Return counter
End Function

  如你所料如果你為一個Web方法激活了Session支持並不意味著其它的Web方法的Session支持也被激活事實上如果Web方法的EnableSession選項沒有被顯式地設置為true那麼ContextSession屬性的值將是null

假設通過設置nfig文件禁止session那麼即使你在WebMethod屬性中使用了EnableSession選項ContextSession的值也將一直是nullnfig文件中的/configuration/systemweb/sessionState項有一個mode參數它決定了你的程序使用何種方法來保持Session狀態該參數默認設置為InProc這時HttpSessionState對象將簡單地保存在進程的內存區如果被設置為Off那麼程序的Session支持就被關閉了

從服務器端看來的session狀態的有效范圍僅僅是某一個給定的應用程序這就意味著一個HttpSessionState類的實例只能被一個特定用戶向某一個虛擬目錄發出的所有Session被激活的請求所使用也就是說使用同一個會話ID的向其它的虛擬目錄的請求將導致不能找到對應的session對象——因為會話ID不是為該應用程序設定的並不區分對ASPX和ASMX文件的請求直到該請求需要使用Session對象因此理論上你可以在一個Web方法調用和一個普通的ASPX文件之間共享Session狀態信息然而我們將看到也有些客戶端的問題使這個想法變得不那麼容易實現

當設置一個HTTP cookie你可以指定其過期時間過期時間指定在多久的時間內客戶端應該將該cookie回傳給服務器如果一個cookie沒有被設置過期時間那麼它僅僅在該進程處理請求的時間內被回傳例如IE將一直回傳cookie除非你關閉了浏覽器的特定窗口的用來保存會話ID的Cookie沒有過期時間因此如果一台客戶機上的多個進程向你的服務器上發送HTTP請求它們也不會共享同一個HttpSessionState對象甚至兩個進程同時運行也是這樣

如果你要處理來自同一個進程的並發的Web Service調用那麼這些請求將在服務器上被排序從而使得在某一時刻只有一個請求被執行的Web Service不像普通ASPX頁面支持允許多請求的並發進程的對HttpSessionState對象的只讀訪問所有Session被激活的Web方法調用都具有read/write訪問的權限因此必須對之進行排序

 客戶端的問題

在你的WebService中成功的使用HttpSessionState的功能事實上依賴於對用戶的一些假設首先也是最重要的一點如果你是用默認的HTTP Cookie模式來保存Session狀態你的客戶端就必須支持cookie如果你是用無cookie的機制來支持Session那麼你的客戶端必須能夠並且願意重定向到一個新的URL該URL由原來的URL中插入會話ID而得到結果將表明這並不是一個無足輕重的問題它關系到你能否成功地部署你的程序

  所有工作都依賴於浏覽器

如果你是用Microsoft Visual 來開發 Web Service應用程序那麼默認的調試方法就是打開IE訪問你的asmx文件通常系統將提供一個可以調用你的Web方法的友好的界面這是一個調試你的Web Service代碼的很好的途徑如果你已經將Web方法的EnableSession選項設置為true它被非常漂亮地支持甚至如果你打開了無cookie的Session支持客戶端浏覽器也可以完美地完成這項工作你的Session對象將如你所願地工作

然而大多數的Web Service請求不是來自浏覽器而是來自應用程序中的Web引用我們如何使框架的添加Web引用的特性呢?讓我們來看一看

  添加Web引用的問題

我將使用我們前面看到的代碼段來創建一個簡單的XML Web Service記起來了吧?這個Web方法被稱作IncrementSessionCounter它僅僅是簡單地把一個整數存儲在HttpSessionState對象中然後每次調用則將它加並且返回當前值從客戶端浏覽器我們可以看到這個數字的值隨著調用次數的增加而增加

下一步我創建了一個簡單的WinForm應用程序並且將上述的Web Service添加到Web引用中下面就是調用我的Web Service的代碼

這裡並沒有與Session打交道
Private Sub Button_Click(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles ButtonClick
Dim proxy As New localhostService()
Dim ret As Integer
ret = proxyIncrementSessionCounter()
LabelText = Result: & CStr(ret)
End Sub
當我第一次調用Web Service時一切正常Web方法返回這就是那個Session變量的應有的初始值現在我點擊Button來再次調用這個Web方法我希望看到的返回值是可惜的是無論我點擊多少次Button返回值一直都是

你也許會懷疑原因就是我每次都創建了一個新的proxy類的實例去調用Web方法因此每次我點擊按鈕都會丟失上一次調用時的cookie不幸的是即使你將proxy類的初始化代碼移到窗體的構造函數中然後對每次Web方法調用使用同一個proxy類的實例你還是不可能看到返回值有增加的跡象


問題在於cookieWeb Service代碼並未從調用請求中發現有效的會話ID因此它每次被調用都創建一個全新的HttpSessionState對象並且返回它的初始值因為作為客戶端的proxy類是從類SystemWebServiceProtocolsSoapHttpClientProtocol繼承的它不包含SystemNetCookieContainer類的實例因此沒有地方來存放返回的cookie為了解決這個問題我對代碼做了如下一些修改

使用了ASPNET的session
但是並不是無Cookie的session
Private Cookies As SystemNetCookieContainer

Private Sub Button_Click(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles ButtonClick
Dim proxy As New localhostService()
Dim ret As Integer
為proxy類設置cookie容器
If Cookies Is Nothing Then
Cookies = New SystemNetCookieContainer()
End If
proxyCookieContainer = Cookies
ret = proxyIncrementSessionCounter()
LabelText = Result: & CStr(ret)
End Sub

  現在代碼工作正常了!每點擊一次Button我都可以看到返回值增加注意到我並不是在函數中聲明變量Cookies的它是窗體類的一個私有成員因為如果希望每次都返回同一個會話ID給服務器的話就必須在每次請求中使用CookieContainer類的同一個實例這就解釋了為什麼SoapHttpClientProtocol類默認不自動地設置的cookie容器正應為此你可以在多個SoapHttpClientProtocol類的實例中共享一個cookie容器而不是為其每個實例自動地創建一個新的cookie容器

無cookie的Session

從Web Service的開發者的角度來看你可以想到相當多的人在試圖使用你的Web服務時忘記在客戶端代理類中添加Cookie容器聰明的開發者或許靈光一閃就會發現無cookie的Session應該可以出色地解決這個問題如果將nfig文件中sessionState元素的cookieless參數設置為true你將會發現通過浏覽器界面調用Web方法時session變量工作正常但是如果你在Visual 中通過添加Web引用來調用它時依然存在著一些問題

為了研究無cookie的session我決定使用上面已經使用過的代碼看看它能否在session狀態被設置為cookieless的服務器環境中能否工作正常我也不想費心去刪除cookie容器的相關代碼因為我希望得到能在兩種session狀態下都正常工作的代碼作為一個天生的樂觀主義者我一個字也不改就直接運行它令人失望的事發生了——不過也不是完全沒有想到我不得不面對這個異常

An unhandled exception of type SystemNetWebException occurred in systemwebservicesdll

Additional information: The request failed with the error message:

<html><head><title>Object moved</title></head><body>
<h>Object moved to <a /HttpSessionState/(lzpsnhhcfoahmaip)/serviceasmx>here</a></h
</body></html>
發生了什麼呢?原來HTTP請求收到的不是 OK響應如果你熟悉HTTP協議你或許可以從響應中的HTML代碼中發現這是一個 Found響應這意味著該請求被重定向到超鏈接中指定的地址返回HTML代碼是很明智的這樣如果一個浏覽器因為某些原因不支持重定向的話它可以把代碼顯示出來或者在重定向過程中顯示這些代碼直到重定向完成注意到超鏈接中包含了一個有趣的字符串(lzpsnhhcfoahmaip)顯然我們可以推斷這就是的會話ID它被嵌入了我們要重定向到的位置的URL中在客戶端代理中我們需要做的僅僅是重新發送請求到這個新的URL

無須再在Win WinInet API編程中跋涉我們可以直接找到proxy類的一個屬性允許自動重定向用外行人的說法就是如果我們接收到一個 Found響應就直接將請求重新發送到相應中HTTP位置頭所指示的URL當Visual 的智能提示顯示proxy類的AllowAutoRedirect屬性時我感到這東西真是機靈得可愛我馬上就在代碼中加上如下一行

  proxyAllowAutoRedirect = True

我認為這仍然比創建一個CookieContainer類並關聯到proxy類要容易得多於是我又一次運行程序很不幸我遭遇了如下異常(為了簡潔起見有所刪節)

An unhandled exception of type SystemInvalidOperationException occurred
in systemwebservicesdll

Additional information: Client found response content type of text/html; charset=utf
but expected text/xml

The request failed with the error message: …
如果你看到錯誤消息的內容你會發現你所看到的HTML頁面跟你浏覽ASMX文件的頁面一樣問題是為什麼當我傳送XML(以SOAP封裝了的形式)到Web Service服務器時它返回的卻是HTML代碼?結果證實你並沒有在SOAP封裝中發送HTTP POST請求而僅僅發送了一個簡單的沒有內容的HTTP GET請求因此你的Web Service服務端理所當然地假設這個請求來自浏覽器於是它返回普通的HTML響應為什麼會這樣呢?

如果你了解HTTP協議你會發現一個HTTP客戶端在收到 Found響應時發送HTTP GET請求到響應中指定的地址是合情合理的即使初始請求是HTTP POST這種方式下浏覽器工作得很好因為開始幾乎所有的請求都是HTTP GET類型的只有當你試圖傳遞數據到一個URL時才會出現上述失敗的結果

理由是在傳送的數據中可能包含潛在的敏感數據因此你需要確認是否用戶真的想向新的資源傳送數據顯然如果你轉向基於重定向設置的新地址你就沒能確認用戶是否真的允許將他們的數據發送到新的地址因此數據並沒有被發送而代之以簡單的HTTP GET請求

我對代碼做了如下修改捕獲 Found異常提示用戶同意重定向他們的請求然後再次在新的位置調用我的Web方法

同時使用基於Cookie和Cookie的Session
Private Cookies As SystemNetCookieContainer
Private webServiceUrl as Uri

Private Sub Button_Click(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles ButtonClick
Dim proxy As New localhostService()
Dim ret As Integer
設置proxy類的Cookie容器
If Cookies Is Nothing Then
Cookies = New SystemNetCookieContainer()
End If
proxyCookieContainer = Cookies
設置proxy類的URL
If webServiceUrl Is Nothing Then
webServiceUrl = New Uri(proxyUrl)
Else
proxyUrl = webServiceUrlAbsoluteUri
End If
Try
ret = proxyIncrementSessionCounter()
Catch we As WebException
如果我們想檢測HTTP狀態碼
那麼就需要一個HttpWebResponse類的實例
If TypeOf weResponse Is HttpWebResponse Then
Dim HttpResponse As HttpWebResponse
HttpResponse = weResponse
If HttpResponseStatusCode = HttpStatusCodeFound Then
這是一個 Found響應提示用戶是否進行重定向
If MsgBox(StringFormat(redirectPrompt _
HttpResponseHeaders(Location)) _
MsgBoxStyleYesNo) = _
MsgBoxResultYes Then
用戶選擇Yes重新嘗試新的URL
webServiceUrl = New Uri(webServiceUrl _
HttpResponseHeaders(Location))
Button_Click(sender e)
Return
End If
End If
End If
Throw we
End Try
LabelText = Result: & CStr(ret)
End Sub
現在的Session工作正常了在你的應用程序中你可以根據情況自行決定是否提示用戶重定向HTTP POST請求舉一個例子如果你正在調用一個Web Service你也許就不希望出現任何可見的對話框

這樣看來要使的Session完全正常地工作還真不是很容易但是應該意識到上面的代碼所展示的原理在其他情況下一樣的有用例如任何平台上的任何Web Service只要它使用HTTP cookie都需要一個cookie容器類似地也許有很多其他的原因導致當你向一個Web Service服務器發送請求時收到 Found響應在一個復雜的應用程序中調用Web Service時可能有許多特殊的情形需要你去處理cookie和重定向的問題就是兩種這樣的情形你應該將之作為你的Web Service調用代碼中最基本的部分

 結 論

  在你調用Web方法的過程中對狀態保持是非常有用的你必須意識到當你使用手邊的浏覽器界面測試你的Web Service時你並沒有面對客戶端程序必須處理的問題幸運的是這些問題並不是很難解決


From:http://tw.wingwit.com/Article/program/net/201311/12874.html
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.