| [VB.net] <WebMethod(EnableSession:=True)> _ Public Function IncrementSessionCounterX() As Integer Dim counter As Integer If Context.Session("Counter") Is Nothing Then counter = 1 Else counter = Context.Session("Counter") + 1 End If Context.Session("Counter") = counter Return counter End Function |
如你所料,如果你为一个Web方法激活了Session支持,并不意味着其它的Web方法的Session支持也被激活。事实上,如果Web方法的EnableSession选项没有被显式地设置为true,那么Context.Session属性的值将是null。
假设通过设置web.config文件禁止session,那么即使你在WebMethod属性中使用了EnableSession选项,Context.Session的值也将一直是null。web.config文件中的/configuration/system.web/sessionState项有一个mode参数,它决定了你的ASP.net程序使用何种方法来保持Session状态。该参数默认设置为“InProc”,这时HttpSessionState对象将简单地保存在ASP.net进程的内存区。如果被设置为“Off”,那么ASP.net程序的Session支持就被关闭了。
从服务器端看来,ASP.net的session状态的有效范围仅仅是某一个给定的ASP.net应用程序,这就意味着一个HttpSessionState类的实例只能被一个特定用户向某一个虚拟目录发出的所有Session被激活的ASP.net请求所使用,也就是说,使用同一个会话ID的向其它的虚拟目录的请求将导致ASP.net不能找到对应的session对象——因为会话ID不是为该ASP.net应用程序设定的。ASP.net并不区分对ASPX和ASMX文件的请求,直到该请求需要使用Session对象,因此,理论上你可以在一个Web方法调用和一个普通的ASPX文件之间共享Session状态信息。然而,我们将看到也有些客户端的问题使这个想法变得不那么容易实现。
当设置一个HTTP cookie,你可以指定其过期时间。过期时间指定在多久的时间内,客户端应该将该cookie回传给服务器。如果一个cookie没有被设置过期时间,那么它仅仅在该进程处理请求的时间内被回传。例如,IE将一直回传cookie,除非你关闭了浏览器的特定窗口。ASP.net的用来保存会话ID的Cookie没有过期时间,因此,如果一台客户机上的多个进程向你的服务器上发送HTTP请求,它们也不会共享同一个HttpSessionState对象,甚至两个进程同时运行也是这样。
如果你要处理来自同一个进程的并发的Web Service调用,那么这些请求将在服务器上被排序,从而使得在某一时刻只有一个请求被执行。ASP.net的Web Service不像普通.ASPX页面,支持允许多请求的并发进程的对HttpSessionState对象的只读访问,所有Session被激活的Web方法调用都具有read/write访问的权限,因此必须对之进行排序。
客户端的问题
在你的WebService中成功的使用HttpSessionState的功能事实上依赖于对用户的一些假设。首先,也是最重要的一点,如果你是用默认的HTTP Cookie模式来保存Session状态,你的客户端就必须支持cookie;如果你是用无cookie的机制来支持Session,那么你的客户端必须能够并且愿意重定向到一个新的URL,该URL由原来的URL中插入会话ID而得到。结果将表明,这并不是一个无足轻重的问题,它关系到你能否成功地部署你的程序。
所有工作都依赖于浏览器
如果你是用Microsoft Visual Studio.net来开发ASP.net Web Service应用程序,那么默认的调试方法就是打开IE访问你的.asmx文件。通常,系统将提供一个可以调用你的Web方法的友好的界面,这是一个调试你的Web Service代码的很好的途径。如果你已经将Web方法的EnableSession选项设置为true,它被非常漂亮地支持,甚至如果你打开了无cookie的Session支持,客户端浏览器也可以完美地完成这项工作,你的Session对象将如你所愿地工作。
然而,大多数的Web Service请求不是来自浏览器,而是来自应用程序中的Web引用。我们如何使用.net框架的“添加Web引用”的特性呢?让我们来看一看。
添加Web引用的问题
我将使用我们前面看到的代码段来创建一个简单的XML Web Service。记起来了吧?这个Web方法被称作IncrementSessionCounter,它仅仅是简单地把一个整数存储在HttpSessionState对象中,然后每次调用则将它加1,并且返回当前值。从客户端浏览器我们可以看到这个数字的值随着调用次数的增加而增加。
下一步,我创建了一个简单的WinForm应用程序,并且将上述的Web Service添加到Web引用中。下面就是调用我的Web Service的代码:
| ' 这里并没有与Session打交道 Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Dim proxy As New localhost.Service1() Dim ret As Integer ret = proxy.IncrementSessionCounter() Label1.Text = "Result: " & CStr(ret) End Sub |
| ' 使用了ASP.NET的session ' 但是并不是无Cookie的session. Private Cookies As System.Net.CookieContainer Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Dim proxy As New localhost.Service1() Dim ret As Integer ' 为proxy类设置cookie容器 If Cookies Is Nothing Then Cookies = New System.Net.CookieContainer() End If proxy.CookieContainer = Cookies ret = proxy.IncrementSessionCounter() Label1.Text = "Result: " & CStr(ret) End Sub |
无cookie的Session
从Web Service的开发者的角度来看,你可以想到相当多的人在试图使用你的Web服务时忘记在客户端代理类中添加Cookie容器。聪明的开发者或许灵光一闪,就会发现无cookie的Session应该可以出色地解决这个问题。如果将web.config文件中sessionState元素的cookieless参数设置为“true”,你将会发现,通过浏览器界面调用Web方法时,session变量工作正常,但是如果你在Visual Studio.net中通过“添加Web引用”来调用它时,依然存在着一些问题。
为了研究无cookie的session,我决定使用上面已经使用过的代码,看看它能否在session状态被设置为cookieless的服务器环境中能否工作正常。我也不想费心去删除cookie容器的相关代码,因为我希望得到能在两种session状态下都正常工作的代码。作为一个天生的乐观主义者,我一个字也不改就直接运行它。令人失望的事发生了——不过也不是完全没有想到,我不得不面对这个异常:
| An unhandled exception of type 'System.Net.WebException' occurred in system.web.services.dll Additional information: The request failed with the error message: -- <html><head><title>Object moved</title></head><body> <h2>Object moved to <a href='/HttpSessionState/(l2z3psnhh2cf1oahmai44p21)/service1.asmx'>here</a>.</h2> </body></html> |
| An unhandled exception of type 'System.InvalidOperationException' occurred in system.web.services.dll Additional information: Client found response content type of 'text/html; charset=utf-8', but expected 'text/xml'. The request failed with the error message: … |
| ' 同时使用基于Cookie和Cookie的Session Private Cookies As System.Net.CookieContainer Private webServiceUrl as Uri Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Dim proxy As New localhost.Service1() Dim ret As Integer ' 设置proxy类的Cookie容器 If Cookies Is Nothing Then Cookies = New System.Net.CookieContainer() End If proxy.CookieContainer = Cookies ' 设置proxy类的URL If webServiceUrl Is Nothing Then webServiceUrl = New Uri(proxy.Url) Else proxy.Url = webServiceUrl.AbsoluteUri End If Try ret = proxy.IncrementSessionCounter() Catch we As WebException ' 如果我们想检测HTTP状态码 ' 那么就需要一个HttpWebResponse类的实例 If TypeOf we.Response Is HttpWebResponse Then Dim HttpResponse As HttpWebResponse HttpResponse = we.Response If HttpResponse.StatusCode = HttpStatusCode.Found Then ' 这是一个“302 Found”响应,提示用户是否进行重定向 If MsgBox(String.Format(redirectPrompt, _ HttpResponse.Headers("Location")), _ MsgBoxStyle.YesNo) = _ MsgBoxResult.Yes Then ' 用户选择Yes,重新尝试新的URL webServiceUrl = New Uri(webServiceUrl, _ HttpResponse.Headers("Location")) Button1_Click(sender, e) Return End If End If End If Throw we End Try Label1.Text = "Result: " & CStr(ret) End Sub |