ASP.NET状态管理的总结

来源:互联网 发布:linux延时函数 编辑:程序博客网 时间:2024/06/10 07:41


ASP.NET状态管理的总结

由于HTTP协议的无状态特性,导致在ASP.NET编程中,每个请求都会在服务端从头到执行一次管线过程, 对于ASP.NET页面来说,Page对象都会重新创建,所有控件以及内容都会重新生成, 因此,如果希望上一次的页面状态能够在后续页面中保留,则必需引入状态管理功能。

ASP.NET为了实现状态管理功能,提供了8种方法,可帮助我们在页面之间或者整个用户会话期间保留状态数据。 这些方法分为二类:视图状态、控件状态、隐藏域、Cookie 和查询字符串会以不同方式将数据发送到客户端上。 而应用程序状态、会话状态和配置文件属性(Profile)则会将数据存储到服务端。 虽然每种方法都有不同的优点和缺点,对于小的项目来说,可以选择自己认为最容易使用的方法, 然而,对于有着较高要求的程序,尤其是对于性能与扩展性比较关注的程序来说, 选择不同的方法最终导致的差别可能就非常大了。

在这篇博客中,我将谈谈自己对ASP.NET状态管理方面的一些看法。
注意:本文的观点可能并不合适开发小型项目,因为我关注的不是易用性。

回到顶部

hidden-input

hidden-input 这个名字我是取的,表示所有type="hidden"的input标签元素。 在中文版的MSDN中,也称之为隐藏域 。 hidden-input通常存在于HTML表单之内,它不会显示到页面中, 但可以随表单一起提交,因此,经常用于维护当前页面的相关状态,在服务端我们可以使用Request.Form[]来访问这些数据。

一般说来,我通常使用hidden-input来保存一些中间结果,用于在多次提交中维持一系列状态, 或者用它来保存一些固定参数用来提交给其它页面(或网站)。 在这些场景中,我不希望用户看到这些数据,因此,使用hidden-input是比较方便的。

关于表单的更多介绍可参考我的博客:细说 Form (表单)

在ASP.NET WebForm框架中,我们可以使用HiddenField控件来创建一个hidden-input控件,并可以在服务端操作它, 还可以直接以手写的方式使用隐藏域,例如:

<input type="hidden" name="hidden-1" value="aaaaaaa" /><input type="hidden" name="hidden-2" value="bbbbbbb" /><input type="hidden" name="hidden-3" value="ccccccc" />

另外,我们还可以调用ClientScript.RegisterHiddenField()方法来创建隐藏域:

ClientScript.RegisterHiddenField("hidden-4", "ddddddddd");

输出结果:

<input type="hidden" name="hidden-4" id="hidden-4" value="ddddddddd" />

这三种方法对于生成的HTML代码来说,主要差别在于它们出现位置不同:
1. HiddenField控件:由HiddenField的出现位置来决定(在form内部)。
2. RegisterHiddenField方法:在form标签的开头位置。
3. hidden-input:你写在哪里就是哪里。

优点:
1. 不需要任何服务器资源:隐藏域随页面一起发送到客户端。
2. 广泛的支持:几乎所有浏览器和客户端设备都支持具有隐藏域的表单。
3. 实现简单:隐藏域是标准的 HTML 控件,不需要复杂的编程逻辑。

缺点:
1. 不能在多页面跳转之间维持状态。
2. 用户可见,保存敏感数据时需要加密。

回到顶部

QueryString

查询字符串是存在于 URL 结尾的一段数据。下面是一个典型的查询字符串示例(红色部分文字):

http://www.abc.com/demo.aspx?k1=aaa&k2=bbb&k3=ccc

查询字符串经常用于页面的数据过滤,例如:
1. 给列表页面增加分页参数,list.aspx?page=2
2. 给列表页面增加过虑范围,Product.aspx?categoryId=5
3. 显示特定记录,ProductInfo.aspx?page=3

关于查询字符串的用法,我补充二点:
1. 可以调用HttpUtility.ParseQueryString()来解析查询字符串。
2. 允许参数名重复:list.aspx?page=2&page=3,因此在修改URL参数时,使用替换方式而不是追加。
  关于参数重名的读取问题,请参考我的博客:细说 Request[]与Request.Params[]

优点:
1. 不需要任何服务器资源:查询字符串的数据包含在每个URL中。
2. 广泛的支持:几乎所有的浏览器和客户端设备均支持使用查询字符串传递参数值。
3. 实现简单:在服务端直接访问Request.QueryString[]可读取数据。
4. 页面传值简单:<a href="url">或者 Response.Redirect(url) 都可以实现。

缺点:
1. 有长度限制。
2. 用户可见,不能保存敏感数据。

回到顶部

Cookie

由于HTTP协议是无状态的,对于一个浏览器发出的多次请求,WEB服务器无法区分它们是不是来源于同一个浏览器。所以,需要额外的数据用于维护会话。 Cookie 正是这样的一段随HTTP请求一起被传递的额外数据。 Cookie 是一小段文本信息,它的工作方式就是伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问站点时 Web 应用程序都可以读取的信息。

与hidden-input, QueryString相比,Cookie有更多的属性,许多浏览器可以直接查看这些信息:

由于Cookie拥有这些属性,因此在客户端状态管理中可以实现更多的功能,尤其是在实现客户端会话方面具有不可替代的作用。

关于Cookie的更多讲解,请参考我的另一篇博客:细说Cookie

优点:
1. 可配置到期规则:Cookie可以在客户端长期存在,也可以在浏览器关闭时清除。
2. 不需要任何服务器资源:Cookie 存储在客户端。
3. 简单性:Cookie 是一种基于文本的轻量结构,包含简单的键值对。
4. 数据持久性:与其它的客户端状态数据相比,Cookie可以实现长久保存。
5. 良好的扩展性:Cookie的读写要经过ASP.NET管线,拥有无限的扩展性。

这里我要解释一下Cookie 【良好的扩展性】是个什么概念,比如:
1. 我可以实现把Cookie保存到数据库中而不需要修改现有的项目代码。
2. 把SessionId这样由ASP.NET产生的临时Cookie让它变成持久保存。

缺点:
1. 大小受到限制。
2. 增加请求头长度。
3. 用户可见,保存敏感数据时需要加密。

回到顶部

ApplicationState

应用程序状态是指采用HttpApplicationState实现的状态维持方式,使用代码如下:

Application.Lock();Application["PageRequestCount"] = ((int)Application["PageRequestCount"]) + 1;Application.UnLock();

对于这种方法,我不建议使用,因为:
1. 与使用静态变量差不多,直接使用静态变量可以不需要字典查找。
2. 选择强类型的集合或者变量可以避免装箱拆箱。

回到顶部

ViewState,ControlState

视图状态,控件状态,二者是类似,在页面中表现为一个hidden-input元素:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="......................" />

控件状态是ASP.NET 2.0中引入,与视图状态相比,它不允许关闭。
由于它们使用方式一致,而且视图状态是基于控件状态的实现逻辑,所以我就不区分它们了。

在ASP.NET的早期,微软为了能帮助广大开发人员提高开发效率,引用入一大批的服务端控件,并为了能将事件编程机制引入ASP.NET中,又发明了ViewState。

这种方式虽然可以简化开发工作量,然而却有一些限制和缺点:
1. 视图状态的数据只能用于回发(postback)。
2. 视图状态的【滥用】容易导致生成的HTML较大,这会引起一个恶性循环:
  a. 过大的ViewState在序列化过程中会消耗较多的服务器CPU资源,
  b. 过大的ViewState最终生成的HTML输出也会很大,它会浪费服务端网络资源,
  c. 过大的ViewState输出导致表单在下次提交时,会占用客户端网络资源。
  d. 过大的ViewState数据上传到服务端后,反序列化又会消耗较多的服务器CPU资源。
  因此,整个交互过程中,用户一直在等待,用户体验极差。

在ASP.NET兴起的年代,ViewState绝对是个了不起的发明。
然而,现在很多关于ASP.NET性能优化的方法中,都会将【关闭ViewState】放在头条位置。
为什么会这样呢,大家可以自己思考一下了。

有些人认为:我现在做的程序只是在局域网内使用,使用ViewState完全没有问题!
然而,那些人或许没有想过:
1. 未来用户可能会把它部署在互联网上运行(对于产品来说就是遇到大客户了)。
2. 项目早期的设计与规划,对后期的开发与维护来说,影响是巨大的,因为许多基础部分通常是在早期开发的。
当这二种情况的任何一种发生时,想再禁用ViewState,可能已经晚了。

对于视图状态,我认为它引入的问题比它解决的问题要多要复杂,
因此,我不想花时间整理它的优缺点,我只想说一句:把它关了,在web.config中关了。

另外,我不排斥使用服务器控件,我认为:你可以使用服务端控件显示数据,但不要用它处理回发。

如果你仍然认为视图状态是不可缺少的,那我还是建议你看看ASP.NET MVC框架,看看没有视图状态是不是照样可以写ASP.NET程序。

回到顶部

Session

Session是ASP.NET实现的一种服务端会话技术,它允许我们方便地在服务端保存与用户有关的会话数据。

我认为Session只有一个优点:最简单的服务端会话实现方式。

缺点:
1. 当mode="InProc"时,容易丢失数据,为什么?因为网站会因为各种原因重启。
2. 当mode="InProc"时,Session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大。
3. 当mode="InProc"时,程序的扩展性会受到影响,原因很简单:服务器的内存不能在多台服务器间共享。
4. 当采用进程外模式时,在每次请求中,不管你用不用会话数据,所有的会话数据都为你准备好了(反序列化),这其实很是浪费资源的。
5. 如果你没有关闭Session,SessionStateModule就一直在工作中,尤其是全采用默认设置时,会对每个请求执行一系列的调用,浪费资源。
6. 阻塞同一客户端发起的多次请求(默认方式)。
7. 无Cookie会话可能会丢失数据(重新生成已过期的会话标识符)。

Session的这些缺点也提醒我们:
1. 当网站的在线人数较多时,一定不要用Session保存较大的对象。
2. 在密集型的AJAX型网站或者大量使用iframe的网站中,要关注Session可能引起的服务端阻塞问题。
3. 当采用进程外模式时,不需要访问Session的页面,一定要关闭,否则会浪费服务器资源。

如果想了解更多的Session特点,以及我对Session的看法,可以浏览我的博客:Session,有没有必要使用它?

Session的本质有二点:
1. SessionId + 服务端字典:服务端字典保存了某个用户的所有会话数据。
2. 用SessionId识别不同的客户端:SessionId通常以Cookie形式发送到客户端。

我认为了解Sesssion本质非常有用,因为可以借鉴并实现自己的服务端会话方法。

关于Session我还想说一点:
有些新手喜欢用Session来实现身份认证功能,这是一种【不正确】的方法。
如果你的ASP.NET应用程序需要身份认证功能,请使用 Forms身份认证 或者 Windows身份认证

回到顶部

Profile

Profile 在中文版的MSDN中被称为 配置文件属性,这个功能是在 ASP.NET 2.0 中引入的。

ASP.NET提供这个功能主要是为了简化与用户相关的个性化信息的读写方式。
简化主要体现在3个方面:
1. 自动与某个用户关联,已登录用户或者未登录都支持。
2. 不需要我们设计用户的个性化信息的保存表结构,只要修改配置文件就够了。
3. 不需要我们实现数据的加载与保存逻辑,ASP.NET框架替我们实现好了。

为了使用Profile,我们首先在web.config中定义所需要的用户个性化信息:

<profile>    <properties>        <add name="Address"/>        <add name="Tel"/>    </properties></profile>

然后,就可以在页面中使用了:

为什么会这样呢?
原因是ASP.NET已经根据web.config为我们创建了一个新类型:

using System;using System.Web.Profile;public class ProfileCommon : ProfileBase{    public ProfileCommon();    public virtual string Address { get; set; }    public virtual string Tel { get; set; }    public virtual ProfileCommon GetProfile(string username);}

有了这个类型后,当我们访问HttpContext.Profile属性时,ASP.NET会创建一个ProfileCommon的实例。 也正是由于Profile的强类型机制,在使用Profile时才会有智能提示功能。

如果我们希望为未登录的匿名用户也提供这种支持,需要将配置修改成:

<profile>    <properties>        <add name="Address" allowAnonymous="true" />        <add name="Tel" allowAnonymous="true"/>    </properties></profile><anonymousIdentification enabled="true" />

Profile中的每个属性还允许指定类型和默认值,以及序列化方式,因此,扩展性还是比较好的。

尽管Profile看上去很美,然而,使用Profile的人却很少。
比如我就不用它,我也没见有人有过它。
为什么会这样?

我个人认为:它与MemberShip一样,是个鸡肋。
通常说来,我们会为用户信息创建一张User表,增加用户信息时,会通过增加字段的方式解决。
我认为这样集中的数据才会更好,而不是说,有一部分数据由我维护,另一部分数据由ASP.NET维护。

另一个特例是:我们根本不创建User表,直接使用MemberShip,那么Profile用来保存MemberShip没有信息是有必要的。

还是给Profile做个总结吧:
优点:使用简单。
缺点:不实用。

回到顶部

各种状态管理的对比与总结

前面分别介绍了ASP.NET的8种状态管理技术,这里打算给它们做个总结。

 客户端服务端数据安全性差好数据长度限制有受硬件限制占用服务器资源否是集群扩展性好差

表格中主要考察了数据保存与服务端水平扩展的相关重要指标。

下面我来解释表格的结果。
1. 客户端方式的状态数据(hidden-input, QueryString, Cookie):
  a. 数据对用户来说,可见可修改,因此数据不安全。
  b. QueryString, Cookie 都有长度限制。
  c. 数据在客户端,因此不占用服务端资源。这个特性对于在线人数很多的网站非常重要。
  d. 数据在客户端,因此和服务端没有耦合关系,WEB服务器可以更容易实现水平扩展。

2. 服务端方式的状态数据(ApplicationState,ViewState,ControlState,Session,Profile):
  a. 数据对用户不可见,因此安全性好。(ApplicationState,Session,Profile)
  b. 数所长度只受硬件限制,因此,对于在线人数较多的网站,需谨慎选择。
  c. 对于存放在内存中的状态数据,由于不能共享内存,因此会限制水平扩展能力。
  d. 如果状态数据保存到一台机器,会有单点失败的可能,也会限制了水平扩展能力。

从这个表格我们还可以得到以下结论:
1. 如果很关注数据的安全性,应该首选服务端的状态管理方法。
2. 如果你关注服务端的水平扩展性,应该首选客户端的状态管理方法。

回到顶部

会话状态的选择

接下来,我们再来看看会话状态,它与状态管理有着一些关系,属于比较类似的概念。

谈到会话状态,首先我要申明一点:会话状态与状态不是一回事。

本文前面所说的状态分为二种:
1. 页面之间的状态。
2. 应用程序范围内的状态。

而会话状态是针对某个用户来说,他(她)在多次操作之间的状态。
在用户的操作期间,有可能状态需要在页面之间持续使用,
也有可能服务端程序做过重启,但数据仍然有效。
因此,这种状态数据更持久。

在ASP.NET中,使用会话状态有二个选择:Session 或者 Cookie 。
前者由ASP.NET实现,并有可能依赖后者。
后者则由浏览器实现,ASP.NET提供读写方法。

那么到底选择哪个呢?
如果你要问我这个问题,我肯定会说:我选 Cookie !

下面是我选择Cookie实现会话状态的理由:
1. 不会有服务端阻塞问题。
2. 不占用服务端资源。
3. 水平扩展没有限制。
4. 也支持过期设置,而且更灵活。
5. 可以在客户端直接使用会话数据。
6. 可以实现更灵活的会话数据加载策略。
7. 扩展性较好(源于ASP.NET管线的扩展性)

如果选择使用Cookie实现会话状态,有3点需要特别注意:
1. 不建议保存敏感数据,除非已加密。
2. 只适合保存短小简单的数据。
3. 如果会话数据较大,可以在客户端保存用户标识,由服务端实现数据的加载保存逻辑。

或许有些人认为:每种技术都有它们的优缺点,有各自的适用领域。
我表示赞同这句话。
但是,我们要清楚一点:每个项目的规模不一样,性能以及扩展性要求也不同。
对于一个小的项目来说,选择什么方法都不是问题,
但是,对于规模较大的项目,我们一定需要取舍。
取舍的目标是:包装越少越好,因为人家做了过多的包装,就会有较多的限制,
所以,不要只关注现在的调用是否方便,其实只要你愿意包装,你也可以让复杂的调用简单化。

回到顶部

改变开发方式,发现新方法

回想一下:为什么在ASP.NET中需要状态管理?
答:因为与HTTP协议有关,服务端没有保存每个请求的上次页面状态。

为什么Windows计算器(这类)程序不用考虑会话问题呢?
答:因为这类程序的界面不需要重新生成,任何变量都可表示状态。

再来看这样一个场景:

图片左边是一个列表页面,允许调整每条记录的优先级,但是有2个要求:
1. 在移动每条记录时,必须输入一个调整理由。
2. 只要输入理由后,那条记录可以任意调整多次。

显然,完成这个任务必须要有状态才能实现。

面对这个问题,你可以思考一下:选择哪种ASP.NET支持的状态管理方法都很麻烦。

怎么办?

我的解决方法:创建一个JavaScript数组,用每个数组元素保存每条记录的状态,
所有用户交互操作用AJAX方式实现,这样页面不会刷新,JavaScript变量中的状态一直有效。
因此,很容易就能解决这个问题。

这个案例也提醒我们:当发现ASP.NET提供的状态管理功能全部不合适时, 我们需要改变开发方式了。

为什么WEB编程都有【无状态】问题,而桌面程序没有?
我认为与HTTP协议有关,但没有绝对的关系。
只要你能保证页面不刷新,也能像桌面程序那样,用JavaScript变量就能维护页面状态。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 微博账号封停怎么办 阴阳师账号被永久封停怎么办 寒刃2账号被禁用怎么办 输了好多钱我该怎么办 亲朋打鱼别处在玩怎么办 做糯米蛋的蛋清怎么办 水田地没耙地平怎么办 宝宝拉鸡蛋花样大便怎么办 电子琴伴奏区无旋律音怎么办 手机触摸屏摔坏了怎么办 手机充着电玩游戏卡怎么办? 4个月宝宝拉肚子怎么办 6个月宝宝上火怎么办 1月婴儿大便干燥怎么办 椰子鞋350线开了怎么办 打完篮球小腿肌肉酸痛怎么办 衣服穿少了感冒怎么办 侧手翻翻不过去怎么办 生完孩子胯宽了怎么办 小孩户口性质弄错了怎么办 4岁宝宝咳嗽很厉害怎么办 宝宝深夜咳嗽很厉害怎么办 2岁宝宝发热37.6怎么办 篮球气嘴慢跑气怎么办 4个月宝宝偏胖怎么办 4个月婴儿偏胖怎么办 6岁儿童偏胖怎么办 打篮球撞到头颈椎痛怎么办 血燥热引起的皮肤瘙痒怎么办 坐时间久了屁股疼怎么办 屁股坐久了疼怎么办 屁股坐久了痛怎么办 坐多了屁股痛怎么办 上班坐的屁股痛怎么办 膝关节手术后康复膝盖疼怎么办 上下楼膝关节疼膝盖疼怎么办 跑步后关节疼该怎么办 后滚翻翻不过去怎么办 走太多路小腿疼怎么办 踢毽子以后期盖右腿内彻疼怎么办 大学体育选修课挂了怎么办