维护应用程序状态(一):使用浏览器cookie

那些刚刚进行Web编程的开发人员,总是难以理解维护应用程序状态的问题。万维网最基础的协议HTTP协议是一个无状态协议,也就是说,从Web服务器端的角度来看,每一个访问请求都是一个新的用户。HTTP协议不提供任何方法来决定两个访问请求是否是同一个用户发出的。

然而,维护状态对任何Web应用程序来说都是非常重要的。购物车是一个经典的例子。如果希望在多个页面请求之间关联一个购物车和某个用户,就需要一些维护状态的方法。

这一系列关注三种包含在ASP.NET 2.0 Framework中的、在多个页面请求间关联数据和特定的用户的方法。在第一部分,你将了解到如何创建和处理浏览器cookie。浏览器cookie用于关联一些文本到网站的每一个用户。

接着,你将了解如何使用会话状态(session state)。会话状态用于关联任意类型的对象到任何用户。例如,可以存储购物车对象到会话状态中。

你将了解到如何使用不依赖cookie的会话状态,这样,即使浏览器禁用了cookie,你也能使用会话状态。你还将了解到如何通过开启进程外会话状态,从而让会话状态更健壮(robust)。

最后,我们介绍ASP.NET 2.0 Framework引入的一个新特性:profile对象。profile对象提供一个创建强类型和持久化方式的会话状态的方法。

你将了解到定义用户配置文件的不同方法,以及如何在一个组件内使用profile对象。最后,你将了解如何实现一个自定义profile提供程序。

 

1.1  使用浏览器cookie

cookie是由第一个版本的Netscape浏览器引入。Netscape的开发人员发明了cookie来解决当时折磨互联网的一个问题,那就是没办法赚钱,因为没办法创建一个购物车。

注解:可以从http://home.netscape.com/newsref/std/cookie_spec.html 阅读Netscape的原始cookie定义。

先讲讲cookie是如何工作的。当Web服务器端创建一个cookie时,一个附加的HTTP首部在浏览器显示页面时被发送到浏览器。HTTP首部类似如下形式:

Set-Cookie: message=Hello

Set-Cookie首部使得浏览器创建一个名为message的cookie,包含值Hello。

在浏览器创建cookie后,它从相同的应用程序请求页面时,都将像下面这样发送这个HTTP首部:

Cookie:message=Hello

cookie头包含所有的Web服务器端设置的cookie。每次浏览器向Web服务器端请求页面时,cookie都会发送回服务器端。

注意,一个cookie就是一段文本。cookie只能用于存储字符串值。

实际上,我们可以存储两种类型的cookie:会话cookie和持久化cookie。一个会话cookie只存在于内存中。当用户关闭浏览器时,会话cookie就永远消失了。

而持久化cookie可以存在几个月甚至几年。当创建一个持久化cookie时,cookie被浏览器长久存在用户的电脑上。以IE为例,cookie以一组文本文件的形式保存在下面的文件夹:

\Documents and Settings\[user]\Cookies

另一方面,对于Mozilla Firefox浏览器则将cookie保存在下面的文件:

\Documents and Settings\[user]\Application Data\Mozilla\Firefox\Profiles\➥[random folder

name]\Cookies.txt

因为不同的浏览器将cookie存于不同的位置,cookie是浏览器独立的。使用IE浏览器请求页面并创建的cookie,不存在于Firefox或Opera浏览器中。

此外,应注意IE和Firefox浏览器都以明文形式存储cookie。我们不应该将诸如社会保险号,或信用卡号码之类的敏感信息存于cookie中。

注解   cookie这个名称从哪里来?根据Netscape最初的cookie文档,cookie并不是因为什么特别的原因被选中的。然而,这个名称最有可能继承自UNIX世界,类似“magic cookie”,在程序间传递的一种不透明的令牌。

1.1.1  cookie的安全性限制

cookie会涉及安全方面的问题。当创建一个持久化cookie时,我们会修改访问者电脑上的文件。有些人就是一天到晚梦想着对你的电脑做些坏事。为了避免电脑遭到袭击,浏览器对cookie有一些强制的安全限制。

首先,所有的cookie是域名独立的。Amazon网站设置的cookie,巴诺书店网站访问不到。浏览器创建一个cookie时,会记录关联到cookie的域名,不会将其发送到另一个域名。

注解   一个包含在Web页面的图片可能来自和页面不同的另一个域名。当浏览器请求一个图片时,cookie可能被另一个域名设置。诸如DoubleClick这样的公司,就利用这样的后门在多个不同的页面间跟踪并显示广告统计信息。这种类型的cookie就称为第三方cookie。

另一个浏览器存储cookie的重要限制是其大小的限制。一个域名存储的cookie总大小不能超过4 096byte。这个大小限制,包含所有的cookie名称和值在内。

注解:IE5.0以上版本支持一个名叫userData行为的功能。userData行为允许存储远大于cookie的数据(对局域网10 240KB,对因特网站点1 024KB)。要了解更多关于userData行为的信息,请访问微软MSDN网站(msdn.microsoft.com)。

最后,大多数浏览器都限制可以被设置的cookie数量,一个域名不超过20个cookie(不包括IE浏览器)。超过这个数目,旧的cookie会被自动删除。

注解:美国白宫管理和财政办公室阻止所有的联邦网站创建持久化cookie,除非“强制需要”。见http://www.whitehouse.gov/omb/memoranda/m00-13.html
美国国家安全应急中心网站(www.nsa.gov)最近就遇到了创建持久化cookie的问题。他们在收到来自隐私倡议的阻止警告后停止了使用持久化cookie。
个人认为这样的cookie偏执策略有点疯狂,不过我们还是要注意它。

因为所有关联到cookie的安全性方面的因素,所有的现代浏览器给用户提供了禁用cookie的选项。这意味着,除非在构建局域网应用程序时我们控制每个人的浏览器,其他时候的默认情况下,不应该企图依赖cookie。我们应尽量只在存储非关键信息时使用cookie。

据说,ASP.NET Framework的许多部分依赖cookie。例如,Web部件、表单验证、会话状态和匿名用户配置文件默认情况下都依赖cookie。如果你使用这些功能中的任何一个,就必须使用cookie。

并且,许多网站也依赖cookie。Yahoo!和MSDN网站的许多网页如果没有开启cookie就访问不了。换句话说,需要访问者开启cookie才能访问我们的网站,并不完全是不合理的要求。

1.1.2  创建cookie

可以通过给Response.Cookies集合添加cookie来创建新的cookie。Response.Cookies集合包含所有Web服务器端发送到Web浏览器的cookie。

例如,代码清单1-1所示的页面将创建一个新的名叫Message的cookie。该页面包含一个输入Message这个cookie的值的表单(见图1-1)。

1-1  创建cookie

代码清单1-1  SetCookie.aspx

< %@ Page  Language ="C#"  % >

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>

< script  runat ="server" >

    protected void btnAdd_Click(object sender, EventArgs e)

    {

        Response.Cookies["message"].Value = txtCookieValue.Text;

    }

</ script >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >

< head  id ="Head1"  runat ="server" >

    
< title > Set Cookie </ title >

</ head >

< body >

    
< form  id ="form1"  runat ="server" >

    
< div >

    

    
< asp:Label

        
id ="lblCookieValue"

        Text
="Cookie Value:"

        AssociatedControlID
="txtCookieValue"

        Runat
="server"   />

    
< asp:TextBox

        
id ="txtCookieValue"

        Runat
="server"   />

    
< asp:Button

        
id ="btnAdd"

        Text
="Add Value"

        OnClick
="btnAdd_Click"  

        Runat
="server"   />

        

    
</ div >

    
</ form >

</ body >

</ html >

注意,cookie的名称是大小写敏感的。设置一个名叫message的cookie和设置一个名叫Message的cookie是不同的。

如果希望修改代码清单1-1所示的页面创建的cookie,可以打开页面,并给Message这个cookie输入一个新的值。当Web服务器端将它发送到浏览器时,修改的cookie值就会被设置到浏览器。

代码清单1-1所示的页面创建的是一个会话cookie(session cookie)。当关闭Web浏览器时,该cookie就消失了。如果希望创建持久化cookie(persistent cookie),则需要为cookie指定一个过期时间。

代码清单1-2所示的页面创建了一个持久化cookie。

代码清单1-2  SetPersistentCookie.aspx

< %@ Page  Language ="C#"  % >

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>

< script  runat ="server" >

    void Page_Load()

    {

        // Get current value of cookie

        int counter = 0;

        if (Request.Cookies["counter"] != null)

            counter = Int32.Parse(Request.Cookies["counter"].Value);

        

        // Increment counter

        counter++;

        

        // Add persistent cookie to browser

        Response.Cookies["counter"].Value = counter.ToString();

        Response.Cookies["counter"].Expires = DateTime.Now.AddYears(2);

        

        // Display value of counter cookie

        lblCounter.Text = counter.ToString();

    }

</ script >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >

< head  id ="Head1"  runat ="server" >

    
< title > Set Persistent Cookie </ title >

</ head >

< body >

    
< form  id ="form1"  runat ="server" >

    
< div >

    

    You have visited this page

    
< asp:Label  

        
id ="lblCounter"

        Runat
="server"   />

    times!

    

    
</ div >

    
</ form >

</ body >

</ html >

代码清单1-2所示的页面记录页面被请求的次数。一个名叫counter的持久化cookie被用来记录页面被请求的次数。注意,counter的过期时间被设为2年以后。当为某个cookie设置了过期时间后,cookie就被保存为持久化cookie了。

 

1.1.3  读取cookie

可以使用Response.Cookies集合创建和修改cookie,也可以使用Request.Cookies集合读取cookie值。

例如,代码清单1-3所示的页面读取message cookie的值。

代码清单1-3  GetCookie.aspx

< %@ Page  Language ="C#"  % >

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>

< script  runat ="server" >

    void Page_Load()

    {

        if (Request.Cookies["message"] != null)

            lblCookieValue.Text = Request.Cookies["message"].Value;

    }

    

</ script >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >

< head  id ="Head1"  runat ="server" >

    
< title > Get Cookie </ title >

</ head >

< body >

    
< form  id ="form1"  runat ="server" >

    
< div >

    

    The value of the message cookie is:

    
< asp:Label

        
id ="lblCookieValue"

        Runat
="server"   />

    

    
</ div >

    
</ form >

</ body >

</ html >

 

    在代码清单1-3中,IsNothing()函数用来在读取cookie值之前检查cookie是否存在。如果不包含该检查,就有可能获得一个空引用异常。同样,不要忘记cookie的名称是大小写敏感的。

代码清单1-4所示的页面列出包含在Request.Cookies集合中的所有cookie(见图1-2)。

1-2  显示所有cookie列表

代码清单1-4  GetAllCookies.aspx

< %@ Page  Language ="C#"  % >

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>

< script  runat ="server" >

    void Page_Load()

    {

        ArrayList colCookies = new ArrayList();

        for (int i = 0; i 
<  Request.Cookies.Count ; i++)

            colCookies.Add(Request.Cookies[i]);

        grdCookies.DataSource 
= colCookies;

        
grdCookies.DataBind();

    }

</script
>

< html  xmlns ="http://www.w3.org/1999/xhtml"   >

< head  id ="Head1"  runat ="server" >

    
< title > Get All Cookies </ title >

</ head >

< body >

    
< form  id ="form1"  runat ="server" >

    
< div >

    

    
< asp:GridView

        
id ="grdCookies"

        Runat
="server" />

        

    
</ div >

    
</ form >

</ body >

</ html >

 

注意,从Request.Cookies集合迭代获得的有意义的信息只有HasKey,Name和Value属性。其他列都显示了不正确的信息。例如,Expires列总是显示最小日期。浏览器不与页面请求交互这些额外的属性,所以也就获取不了这些属性的值。

使用Request.Cookies集合时,非常重要的一点是,理解For...Each循环返回的值和For...Next循环返回的值不同。如果用For...Each循环迭代Request.Cookies集合,返回的是cookies的名称。如果用For...Next循环迭代该集合,返回的是HeepCookie类(下一节介绍)的示例。

 

1.1.4  设置cookie属性

HttpCookie类代表cookie。当创建或读取一个cookie时,可以使用该类的下面这些属性:

q Domain——用于设置关联到cookie的域名,默认值是当前域名;

q Expires——用于通过给定一个过期时间创建一个持久化cookie;

q HasKeys——用于指定该cookie是否是一个多值cookie(见本章稍后的3.1.6节);

q HttpOnly——用于避免cookie被JavaScript访问;

q Name——用户指定cookie的名称;

q Path——用于指定关联到cookie的路径。默认值为/;

q Secure——用于指定cookie需要通过安全Socket层(SSL)连接传递;

q Value——允许读/写cookie的值;

q Values——当使用多值cookie时,用于读/写特定的值(见本章稍后的3.1.6节)。

这些属性中的一部分需要更多的解释。例如,你可能发现Domain属性有些奇怪,因为你修改不了关联到cookie的domain。

Domain属性对于组织子域名时会非常有用。如果需要设置cookie可以被Sales.MyCompany.com,Managers.MyCompany.com和Support.MyCompany.com访问,则需要设置Domain属性值为.MyCompany.com(注意开头的部分),而不能使用该属性关联cookie到一个完全不同的域名。

HttpOnly属性用户设置一个cookie是否可以通过JavaScript访问。该属性只对IE6(SP1)级以上版本有效。引入该属性是为了防止跨站点脚本攻击。

Path属性用于限定cookie到一个特定的路径。例如,如果在相同的域名部署多个应用程序,而不希望应用程序共享相同的cookie,则需要设置Path属性避免一个应用程序读取另一个应用程序的cookie。

Path属性听起来很有用。不幸的是,你不应该使用它。IE对于路径是大小写敏感的。如果用户在地址栏输入一个不同大小写的路径,则cookie不会被发送。换句话说,下面两个路径并不匹配:

http://localhost/original/GetAllCookies.aspx

http://localhost/ORIGINAL/GetAllCookies.aspx

 

1.1.5  删除cookie

删除一个cookie的方法并不直观。要删除一个存在的cookie,必须设置其过期时间为一个过去的时间。

代码清单1-5所示的页面演示了如何删除一个单值cookie。页面包含一个用于输入cookie名称的表单。当提交表单时,指定名称的cookie就被删除了。

代码清单1-5  DeleteCookie.aspx

< %@ Page  Language ="C#"  % >

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>

< script  runat ="server" >

    protected void btnDelete_Click(object sender, EventArgs e)

    {

        Response.Cookies[txtCookieName.Text].Expires = DateTime.Now.AddDays(-1);

    }

</ script >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >

< head  id ="Head1"  runat ="server" >

    
< title > Delete Cookie </ title >

</ head >

< body >

    
< form  id ="form1"  runat ="server" >

    
< div >

    

    
< asp:Label

        
id ="lblCookieName"

        Text
="Cookie Name:"

        AssociatedControlID
="txtCookieName"

        Runat
="server"   />

    
< asp:TextBox

        
id ="txtCookieName"

        Runat
="server"   />

    
< asp:Button

        
id ="btnDelete"

        Text
="Delete Cookie"

        OnClick
="btnDelete_Click"  

        Runat
="server"   />

        

    
</ div >

    
</ form >

</ body >

</ html >

 

当需要删除cookie时设置的特定的日期只要是一个过去的值,无所谓实际的值是多少。在代码清单1-5中,过期时间被设为一天前。

代码清单1-6所示的页面从浏览器删除当前域名(和路径)下的所有cookie。

代码清单1-6  DeleteAllCookies.aspx

< %@ Page  Language ="C#"  % >

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>

< script  runat ="server" >

    

    void Page_Load()

    {

        string[] cookies = Request.Cookies.AllKeys;

        foreach (string cookie in cookies)

        {

            BulletedList1.Items.Add("Deleting " + cookie);

            Response.Cookies[cookie].Expires = DateTime.Now.AddDays(-1);

        }

    }

</ script >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >

< head  id ="Head1"  runat ="server" >

    
< title > Delete All Cookies </ title >

</ head >

< body >

    
< form  id ="form1"  runat ="server" >

    
< div >

    

    
< h1 > Delete All Cookies </ h1 >

    

    
< asp:BulletedList

        
id ="BulletedList1"

        EnableViewState
="false"

        Runat
="server"   />

    

    
</ div >

    
</ form >

</ body >

</ html >

 

代码清单1-6所示的页面从Request.Cookies集合中循环遍历并删除所有的cookie。

 

1.1.6  使用多值cookie

根据cookie规范,对单个域名,浏览器不能存储超过20个cookie。可以通过创建多值cookie来超越该限制。多值cookie是一个包含子键的单一cookie。可以根据需要创建任意数量的子键。

例如,代码清单1-7所示的页面创建了一个名叫preferences的多值cookie。preferences cookie用于存储first name(名)、last name(姓)和favorite color(喜爱的色彩)(见图1-3)。

1-3  创建多值cookie

代码清单1-7  SetCookieValues.aspx

< %@ Page  Language ="C#"  % >

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>

< script  runat ="server" >

    void btnSubmit_Click(Object s, EventArgs e)

    {

        Response.Cookies["preferences"]["firstName"] = txtFirstName.Text;

        Response.Cookies["preferences"]["lastName"] = txtLastName.Text;

        Response.Cookies["preferences"]["favoriteColor"] = txtFavoriteColor.Text;

        Response.Cookies["preferences"].Expires = DateTime.MaxValue;

    }

</ script >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >

< head  id ="Head1"  runat ="server" >

    
< title > Set Cookie Values </ title >

</ head >

< body >

    
< form  id ="form1"  runat ="server" >

    
< div >

    

    
< asp:Label

        
id ="lblFirstName"

        Text
="First Name:"

        AssociatedControlID
="txtFirstName"

        Runat
="server"   />

    
< br  />     

    
< asp:TextBox

        
id ="txtFirstName"

        Runat
="server"   />

    
< br  />< br  />

    
< asp:Label

        
id ="lblLastName"

        Text
="Last Name:"

        AssociatedControlID
="txtFirstName"

        Runat
="server"   />

    
< br  />     

    
< asp:TextBox

        
id ="txtLastName"

        Runat
="server"   />

    
< br  />< br  />

    
< asp:Label

        
id ="lblFavoriteColor"

        Text
="Favorite Color:"

        AssociatedControlID
="txtFavoriteColor"

        Runat
="server"   />

    
< br  />     

    
< asp:TextBox

        
id ="txtFavoriteColor"

        Runat
="server"   />

    
< br  />< br  />

    
< asp:Button

        
id ="btnSubmit"

        Text
="Submit"

        OnClick
="btnSubmit_Click"

        Runat
="server"   />

    

    

    
</ div >

    
</ form >

</ body >

</ html >

 

当提交代码清单1-7所示的页面时,下面的HTTP首部被发送到浏览器:

Set-Cookie: preferences=firstName=Steve&lastName=Walther&favoriteColor=green;

expires=Fri, 31-Dec-9999 23:59:59 GMT; path=/

代码清单1-8所示的页面从preferences cookie读取值。

代码清单1-8  GetCookieValues.aspx

< %@ Page  Language ="C#"  % >

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>

< script  runat ="server" >

    void Page_Load()

    {

        if (Request.Cookies["preferences"] != null)

        {

            lblFirstName.Text = Request.Cookies["preferences"]["firstName"];

            lblLastName.Text = Request.Cookies["preferences"]["lastName"];

            lblFavoriteColor.Text = Request.Cookies["preferences"]["favoriteColor"];

        }

    }

</ script >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >

< head  id ="Head1"  runat ="server" >

    
< title > Get Cookie Values </ title >

</ head >

< body >

    
< form  id ="form1"  runat ="server" >

    
< div >

    

    First Name:

    
< asp:Label

        
id ="lblFirstName"

        Runat
="server"   />

    
< br  />

    Last Name:

    
< asp:Label

        
id ="lblLastName"

        Runat
="server"   />

    
< br  />

    Favorite Color:

    
< asp:Label

        
id ="lblFavoriteColor"

        Runat
="server"   />

    

    
</ div >

    
</ form >

</ body >

</ html >

 

可以使用HttpCookie.HasKey属性判断一个cookie是一个普通cookie还是一个多值cookie。

你可能感兴趣的:(cookie)