ASP.Net的状态管理
1、查询字符串(Query String)
对于特殊字符,当需要作为查询字符串的一部分时,需要调用Server.UrlEncode方法进行编码,在获取该查询字符串时,需要调用Server.UrlDecode来解码。查询字符串可以用来保存一些简单的信息,但是它的容量有限且有一定的安全风险。
2、隐藏域(HiddenField)
隐藏域可以用于保存一些简单的、不敏感的信息(因为隐藏域保存的信息是可以被用户看到的),为了能够使用并处理隐藏域里的值必须使用Http Post方法提交,而不是用URL链接或者Http Get方法提交。存储过多的信息在隐藏域中会使页面尺寸变大,从而使加载一个页面需要更长的时间,降低了性能。
3、HttpContext.Items
HttpContext的生命周期为一个请求,所以HttpContext.Items的生命周期也就这么长,该属性可以存放任何数据,不过存放时间很短。一般用于两个地方,一个是在同一个请求中的Application里的两个页面之间传递数据(页面间的切换使用Server.Transfer方法或者Server.Execute),另一个是在Http Pipeline中为各种处理保存需要的数据,可以在任何处理方法中用于保存数据,例如Application_BeginRequest方法。
4、Cookie
最常用的方式之一,不过大小以及数量会受到不同程度的限制,因浏览器的不同而异。一个Cookie可以存储多个名值对(Response.Cookies["C1"]["a"]或者cookie.Values["a"],其中cookie是HttpCookie的对象)。一般情况下,所有的页面都可以访问该站点的Cookie,不过也可以通过设置Path属性来限制Cookie只被用于某个路径下的页面,或者通过设置Domain属性来限制Cookie只被用于某些特定的域中的页面。在读取Cookie时,需要判断下是否存在这个Cookie,并且可以使用Server.HtmlEncode方法对Cookie的内容进行编码以防恶意的脚本。另外,各个浏览器存储Cookie的方式不尽相同,因此很有可能无法读取其它浏览器存储的Cookie。Cookie的有效期信息不会随着Cookie的其它信息发送给服务器,因此无法读取到Cookie的有效期。一个Cookie可以有多个名值对,可以通过Values的AllKeys或者Keys属性来获得名字的集合,通过Values来获得值的集合,其中AllKeys属性会有缓存,如果需要多次使用该Cookie的名值对,则AllKeys属性比Keys属性开销小。Cookie其实是无法直接修改的,修改Cookie的方法是创建一个新的Cookie来覆盖旧的Cookie,同样,删除一个Cookie的方法是创建一个新的并且到期时间早于当前时间的Cookie来覆盖旧的Cookie,由于到期时间已过,浏览器会自动删除该Cookie。Cookie的安全性较低,容易获得并被篡改,因而不适于存储敏感的信息。Cookie还会因用户禁用而产生问题。
5、Cache
Cache的生命周期依赖应用程序域的生命周期,如果重启一个应用程序域,则原有的Cache将不复存在。Cache项可以被赋予优先级(CacheItemPriority),在清理Cache项时,优先级低的Cache项会被首先清除。Cache项可以被手动设置过期时间,分为绝对的过期时间和相对的过期时间两种,相对的过期时间会以上次Cache的访问时间为基础,一个Cache项只能被赋予一种过期时间,另一个必须使用Cache.NoAbsoluteExpiration或者Cache.NoSlidingExpiration来表示。对于已经存在的key,Cache.Add方法不会覆盖原来的值而Cache.Insert方法会覆盖原来的值,Cache.Add方法会返回当前Cache项的引用而Cache.Insert无返回值。Cache还可以设置依赖项(CacheDependency类的对象),当依赖项发生变动时,相应的Cache项将被移除,依赖项可以是文件、目录、另一个Cache项等,一个Cache项可以添加多个依赖项(可以使用AggregateCacheDependency类的对象),只要任何一个依赖项发生变动,该Cache项都会被移除。当Cache项被移除或者更新时会触发一个CallBack的事件,可以通过定义该事件的处理方法来进行一些处理。由于Cache项可能会被系统自动移除(如缓存已满,缓存过期或者依赖项发生改变),因此在检索Cache项时首先需要判断它是否为null。asp.net框架会对Cache进行主动管理,即当内存不足时会对Cache进行清理,移除一些Cache项。
6、其他存放数据的物理媒介
例如数据库,XML文件,一般文本文件和注册表等。
7、Application
Application是HttpApplicationState类的一个实例,它是全局存储,使用键/值对进行存储,可以存储各种数据,存储的数据置于内存中,不特定于某个用户而是所有用户都可以访问,通过HttpContext类的Application属性进行访问,当内存不足时,asp.net框架不会对Application进行主动管理,因而不宜将大量数据存储于Application中,可以存储在Cache中。当多线程访问Application时,需要控制并发操作,保证线程安全。Application无法在同一个Web应用中的多个服务器之间或者同一个Web应用同一个服务器中的多个辅助进程之间进行共享。
保证线程安全示例代码:
Application.Lock();
Application["key"] = "value";
Application.UnLock();
8、Session
Session是HttpSessionState类的一个实例,Cookieless的Session ID会嵌在URL的应用程序根目录之后,但在文件和子目录之前,如:http://www.abc.com/axdefjtkdjfkera/default.aspx 当SesstionState为InProc的时候,支持Session_OnEnd事件,如果为StateServer或者SQLServer则忽略Session_OnEnd事件。
9、Profile
Profile是针对每个用户的,而且存储于其中的数据是永久的。Profile可以自定义存储数据的地方,默认情况下,Profile数据存储在Web应用的App_Data目录下的Microsoft SQL Server Express database,通过定义,Profile数据也可以存储在SQL Server或者Oracle数据库中。Profile里的属性都是强类型的,即类型都是确定的,类型可以在machine.config或者web.config里进行设置。对于同一个Web应用只能定义一个Profile。
<configuration>
<system.web>
<authentication mode="Forms" />
<anonymousIdentification enabled="true" />
<profile>
<properties>
<add name="FirstName" defaultValue="??" allowAnonymous="true" />
<add name="LastName" defaultValue="??" allowAnonymous="true" />
<add name="PageVisits" type="Int32" allowAnonymous="true"/>
<group name="Address">
<add name="Street" allowAnonymous="true" />
<add name="City" allowAnonymous="true" />
</group>
<add name="ShoppingCart" type="ShoppingCart" serializeAs="Binary" allowAnonymous="true" />
</properties>
</profile>
</system.web>
</configuration>
Profile属性的默认类型是String,defaultValue属性只可以用于像String和Int32这样的简单类型,复杂类型无法使用该属性。可以通过HttpContext.Profile来操作Profile。Profile.[group name].[property name]。对于复杂类型,该类型必须是可序列化的。对于复杂类型的Profile的属性,必须先检查该属性是否为null,如果为null则需要手动赋给该属性一个实例化的对象,因为系统不会自动为Profile的属性实例化。Profile可以被继承,可以创建一个继承自System.Web.Profile.ProfileBase类的类,并在web.config里设置profile继承自该类。例如:
using System; using System.Web.Profile; public class UserInfo : ProfileBase { private string _FirstName; private string _LastName; public string FirstName { get { return _FirstName; } set { _FirstName = value; } } public string LastName { get { return _LastName; } set { _LastName = value; } } } <configuration>
<system.web>
<anonymousIdentification enabled="true" />
<profile inherits="UserInfo" />
</system.web>
</configuration>
当Profile存储一个匿名用户的信息时,系统会自动为该用户随机生成一个识别码并存储在Cookie里,而当Profile存储一个验证用户信息时,系统会用验证用户信息来作为识别码而不是随机生成一个,因而当一个用户从一个匿名用户变成一个验证用户时,需要将Profile进行移植以保证Profile的一致性,实现移植可以通过Profile Module的MigrateAnonymous事件处理来进行,需要在Global.asax文件里实现一个该事件的处理方法,方法签名为void Profile_MigrateAnonymous(Object s, ProfileMigrateEventArgs e),方法实现例子如下:
void Profile_MigrateAnonymous(Object s, ProfileMigrateEventArgs e)
{
ProfileCommon anonProfile =
Profile.GetProfile(e.AnonymousId);
Profile.FavoriteColor = anonProfile.FavoriteColor;
}
将Profile存储地更改为SQLServer需要两个步骤,
1、在SQL Server那里进行设置,使用aspnet_regsql.exe(存放在Windows\Microsoft.NET\Framework\[version]目录下)进行设置。
2、在Web.config和Machine.config那里进行设置,例子如下:
<configuration>
<connectionStrings>
<add name="myConnectionString" connectionString="Server=MyServer;Trusted_Connection=true;database=MyDatabase" />
</connectionStrings>
<system.web>
<anonymousIdentification enabled="true" />
<profile defaultProvider="MyProfileProvider">
<providers>
<add name="MyProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="myConnectionString" />
</providers>
<properties>
<add name="FirstName" allowAnonymous="true" />
<add name="LastName" allowAnonymous="true" />
</properties>
</profile>
</system.web>
</configuration>
如果需要将Profile存储地更改为Oracle或者MySQL则需要自己实现相应的Profile Provider类,这些Profile Provider类需要继承System.Web.Profile.ProfileProvider抽象类,由于System.Web.Profile.ProfileProvider继承自System.Configuration.SettingsProvider抽象类,而System.Configuration.SettingsProvider继承自System.Configuration.Provider.ProviderBase抽象类,所以具体类需要实现这三个类的必需成员。
System.Web.Profile.ProfileManager类用于管理Profile和生成Profile报告。
10、ViewState
由于ViewState里的数据是以字符串的形式(Base64编码格式)存储,所以存储在ViewState里的对象必须是可序列化的。
由于ViewState信息是存储在一个或者多个隐藏域中的(HiddenField,名字和id都为__VIEWSTATE),所以在PreRenderComplete事件之前都可以修改ViewState。
由于ViewState信息存在于页面上,所以用户可以通过查看页面源码来获得ViewState信息,为了保证ViewState信息的安全,可以通过在@Page指令中将属性ViewStateEncryptionMode设为Always来加密ViewState信息。
ViewState存储的数据只能用于当前页(主要用于PostBack时的数据保存),无法在页面之间进行传送。
由于ViewState中的信息是存储在页面上的隐藏域中的,所以ViewState中存储的信息量过大的话会影响页面的性能,而且会被防火墙和代理阻截,有些移动设备不支持隐藏域,因而ViewState在这些设备上将无效。
ViewState会保存服务器端控件(runat='server')的值,会跟踪值的变化状态,会序列化存储的数据。可以通过Control.ViewState(protected)来操作ViewState,它的类型是StateBag。服务器端控件的大多数属性都是通过ViewState来保存它们的当前值的。可以通过调用Control.TrackViewState()方法来启动对ViewState中保存的每个数据项值的变化的跟踪,跟踪一旦启动就无法关闭,ViewState中的每个数据项值只要一变化就会被设为"Dirty",同时作为StateBag类型的对象,可以通过IsItemDirty(string key)方法来判断该数据项是否为"Dirty",以及SetItemDirty(string key, bool dirty)方法来手动设置该数据项是否为"Dirty",如果跟踪未被启动,则数据项永远不为"Dirty"。当跟踪启动后,即使赋给数据项的值与数据项的原值一样,数据项也会被设为"Dirty"。
对于声明性控件(declarative controls,即在aspx和ascx中已定义的控件),当asp.net解析文件时,如果发现该控件的标签中有runat='server'则会为该控件创建一个实例,该实例的名字基于ID属性的值,但如果ID属性没有设置则asp.net会自动生成一个名字,随后将控件标签里的其他属性的值赋给该实例对应的属性,对于类型不匹配的属性需要先进行转换。在将声明的值赋给对应实例的属性时,ViewState的跟踪还未启动,asp.net是在OnInit事件时启动ViewState跟踪,asp.net使用这一方法来区别声明的值与动态赋予的值。一个页面里的所有控件的架构其实是个树状的结构,因为每个控件可以有自己的子控件,当页面需要保存自己的ViewState时,它会递归调用每个控件的Control.SaveViewState()方法,该方法会返回一个object类型的对象,该对象保存该控件的当前ViewState信息,当树状结构上的所有控件都保存了自己的ViewState后,就会生成一个树状结构的ViewState数据树,在保存控件的ViewState时,只会对标记为"Dirty"的属性进行保存。随后asp.net对保存的ViewState进行序列化,生成在页面源码中可以看见的那个字符串。如果控件属性值未被更改过,即使有PostBack也不会把该属性设为"Dirty"。当页面载入ViewState时,会调用Control.LoadViewState()方法把已经反序列化的数据填充到ViewState中,在调用LoadViewState()方法之前,每个ViewState数据项可能已经有相应的值(默认值或者是手动赋予的值),在LoadViewState()方法调用后会被覆盖并被设为"Dirty",因为LoadViewState()方法是在调用TrackViewState()方法后被调用的,是在OnInit()事件处理时被调用的。
对于静态数据(例如Label上的Text),每次在Page_Load时为它赋值,会导致加入一段没有必要的ViewState数据项,从而导致存储ViewState的序列化字符串变大,影响页面性能。解决方法有两种,一是把控件的"EnableViewState"的属性值设为"false",二是直接在aspx或者ascx页面中显示需要显示的Text值而不使用Label控件,如<%= CurrentUser.Name%>。
对于绑定一个有固定数据的dropdown list,dropdown list里的所有数据项都会作为ViewState的一部分而被加入序列化字符串中,从而影响页面性能,解决方法为首先把dropdown list里的EnableViewState属性值设为false,然后把绑定dropdown list的代码放入OnInit()事件处理方法内并且放在base.OnInit(e)方法之前(这是因为放在Page_Load方法里会导致每次PostBack把dropdown list重新绑定从而丢失了dropdown list的已选值,disable viewstate本身并不会使dropdown list的已选值丢失,放在base.OnInit(e)方法之前是因为该方法会载入dropdown list的已选值,在载入之前绑定是为了保证绑定不会把已选值覆盖掉),不过该方法会导致每次PostBack都要连接一次数据库取值,是否使用该方法需要从性能方面综合考虑哪个性能开销大(每次PostBack连接一次数据库开销大还是ViewState字符串变大导致页面变大开销大)。
在OnInit事件处理时,asp.net先从子控件开始调用事件处理方法,然后是父控件,依此类推。对于子控件的一些属性(如Label的Text属性)有时需要动态初始化(如使用DateTime.Now),为了使该属性的初始化值不被放入ViewState的序列化字符串中从而不影响页面性能,有两种方式可以解决,一是设置子控件的OnInit属性(如Label的OnInit属性) ,因为该属性的处理会先于子控件本身的OnInit事件,然后在该属性处理方法中赋值,二是基于子控件创建一个自定义的控件,然后在自定义控件的构造方法中赋值,因为构造方法先于控件本身的OnInit事件被调用。对于动态添加的子控件,为了确保它的一些属性的初始值不被加入ViewState的序列化字符串中,需要在调用this.Controls.Add()方法之前对这些属性赋值,因为调用this.Controls.Add()方法时,子控件的OnInit事件会被触发从而调用TrackViewState()方法,从而之后的赋值会导致ViewState相应的数据项被设为"Dirty",从而被加入序列化字符串中。
当一个页面在创建或者改动后第一次被访问时asp.net会自动生成一个类,并为该类创建一个编译过的实例,存放在WINDOWS\Microsoft.NET\Framework\[
version]
\Temporary ASP.NET Files目录下,生成这样一个类用于生成整个页面的控件及其架构,控件及其架构的生成是在页面实例化阶段(比初始化阶段Init还要早),在该阶段asp.net还会把控件属性的声明值赋给相应的属性(声明值就是在aspx或者ascx文件中为控件属性添加的值)。之后在Init阶段,整个页面的控件架构自下而上的调用TrackViewState()方法来启动对ViewState的跟踪。接着,如果是PostBack页面,则会对上次页面访问时保存的ViewState进行验证并载入。然后,如果页面是PostBack的并且控件实现了IPostBackDataHandler接口,则之前自动生成的页面类会调用该控件实现的IPostBackDataHandler接口中的LoadPostData()方法来载入PostBack时的数据并赋给控件相应的属性(如TextBox的Text属性),这就是为什么TextBox在PostBack时可以记住用户输入的内容,包括Dropdown List和CheckBox能够记住用户的选择,这与ViewState没有任何关系。PostBack的数据会与ViewState数据一起通过Http Post头部传给服务器端。之后是Load阶段,一般通过Page_Load方法来处理。接下来,如果页面被PostBack,则进入PostBack事件处理阶段,总的来说一共有两种PostBack事件,一种是Changed事件(如Dropdown List的SelectedIndexChanged事件,TextBox的TextChanged事件),控件要想提供这种事件必须实现IPostBackDataHandler接口,另一种是Raised事件(如Button的Click事件),控件要想提供这种事件必须实现IPostBackEventHandler接口。Changed事件的触发是通过比较PostBack的数据和上次页面访问的ViewState数据来进行的。
对控件动态添加一些子控件(如DropDown List中的ListItem),必须保证ViewState开启,不然子控件会丢失,另外对于动态添加的控件,子控件的添加必须晚于该控件被添加,因为当该控件通过Control.Add()方法被添加后才会开启ViewState的跟踪,从而才能保存子控件的添加。在SaveViewState阶段,页面类会递归调用控件的SaveViewState方法来保存ViewState。在Render阶段,页面类会递归调用控件的RenderControl方法来生成HTML代码。
ViewState的开启可以帮助保存控件改变后的属性值(通过代码改变的)。动态加载子控件最好放在LoadViewState阶段前(如Init阶段),当然放在之后也可以,因为Controls.Add方法会自动帮助子控件LoadViewState。对于并非每次postback都会添加的子控件需要将它放在父控件的子控件队列的末尾,因为父控件会保存每个子控件的ViewState并且用Index来区分,如果该子控件插在队列中央会使Index次序被打乱从而使其他子控件无法正确得到它们对应的ViewState。
由于SaveViewState方法只会保存那些被标记为Dirty的ViewState,而只有在调用TrackViewState方法之后,ViewState被改动才会被标记为Dirty,所以在调用TrackViewState方法之前的任何ViewState的改动都不会被保存。TrackViewState方法一般是在OnInit最后被调用。
ViewState的开销有两个方面,一个是需要递归保存且序列化成字符串以及反序列化再递归载入,另一个是会占用页面的空间。
把控件/页面的EnableViewState属性设为false后,该控件/页面就不会保存ViewState,除非控件/页面需要在PostBack时记住一些状态,否则应该把ViewState禁掉。
DataGrid会把它的所有内容存储在ViewState中,从而导致它的ViewState非常大,使用DataGrid的ViewState可以减少在PostBack时连接数据库的次数,因为使用了ViewState后,DataGrid不需要在每次PostBack时重新进行绑定。
asp.net用
LosFormatter类来序列化和反序列化ViewState,这个类可以序列化任何BinaryFormatter可以序列化的类型并且可以高效序列化String,Integer,Boolean,Array,ArrayList,Hashtable,Pair,Triplet类。
Page类的
SavePageStateToPersistenceMedium()和
LoadPageStateFromPersistenceMedium()方法分别用于序列化ViewState并将其保存到页面的隐藏域中和从页面的隐藏域中读取ViewState编码并将其反序列化。可以通过继承Page类并重构这两个方法把ViewState保存在其他存储媒介中,如果重构了这两个方法,则原来存储ViewState的隐藏域就不会被创建,如果把ViewState保存在其他存储媒介中,需要一些方法来管理这些ViewState,例如保存在文件系统中,需要Windows服务来管理这些文件。
页面包含所有它的子控件的ViewState,每个控件包含所有它的子控件的ViewState。页面有一个SavePageViewState()方法,该方法一开始会创建一个Triplet类的对象,用于存储三件东西,
1.页面的Hash Code,这个hash code用于保证ViewState在PostBack时未被篡改。
2.ViewState(通过调用
SaveViewStateRecursive()方法来获得,是一个Triplet类的对象,也保存三件东西
)
a.当前控件的ViewState,可能是Pair或者Pair数组,每个Pair里包含两个ArrayList,一个存名字,一个存值。
b.一个存储Integer的ArrayList,用于保存子控件的索引(子控件的ViewState非null)
c.一个存储子控件ViewState的ArrayList,与索引一一对应。
3.控件列表(用ArrayList类存储,都是些需要被页面显示调用的控件)
为了安全起见,ViewState不应该保存敏感信息(例如密码),asp.net通过使用MAC(Message Authentication Check),即比较ViewState的Hash值来确定ViewState是否被篡改。但是使用Server.Transfer()方法时,ViewState的MAC会失败,这是因为ViewState的验证合法仅限于当前页面,任何跨页的合法验证都会失败,这个问题的解决方法有
1、是通过把属性
EnableViewStateMac设为false来
关闭MAC,但会造成一个安全漏洞。
2、调用Server.Transfer方法时第二个参数设为false或者不添加第二个参数(第二个参数默认值是false),这样就不会把QueryString和Form的数据传送过去,由于无数据传送,也就不会使用MAC。
3、把所有控件的属性值传递过去,这个方法非常繁琐且难以维护。
设置ViewState的Hash算法是通过
<machineKey>
元素中的validation属性来完成的。asp.net的ViewState可以被加密,通过对<machineKey>
元素中的decryption属性来设置加密算法,需要注意的是,在Web Farm模式下,要确保各台Web服务器ViewState使用相同的加密/hash算法以及相同的密钥,可以通过对<machineKey>
元素中的decryptionKey和validationKey属性的设置来完成。
ViewState大多数情况下可以减少系统连接数据库的次数,要想使用ViewState,必须有一个runat=server的form标签,page本身会保存大概20个字节的ViewState用于区分ViewState数据和PostBack数据,所以即使page禁止使用ViewState,仍旧会有一些ViewState数据在页面上保存,如果想彻底没有ViewState数据,那就把页面中的runat=server的form删掉。
ViewState不便于存储大量的数据,敏感的数据和不易序列化的数据。
为了提高页面的性能,在无需ViewState的时候将它禁掉(例如无需PostBack的页面,无需事件处理的控件),充分利用ViewState的序列化器的优势(ViewState序列化器对某些类型的序列化效率很高),减少存储在ViewState中的对象,ViewState可以在控件级,页面级和应用级被禁掉
控件级 | <asp:datagrid EnableViewState="false" ... /> |
---|---|
页面级 | <%@ Page EnableViewState="False" ... %> |
应用级 | <Pages EnableViewState="false" ... />(web.config文件中) |
Page类有一个属性叫做ViewStateUserKey,如果要使用该属性必须在Page_Init时进行设置(赋一个String值),该属性用于避免一个用户怂恿另一个用户访问他已访问过的页面并使用他自己的ViewState,即相当于在hash中加一个salt,增强安全性。
在Load阶段结束后动态添加的控件中的用户输入或修改值将无法在PostBack中被保存。
动态添加的控件必须每次PostBack时都动态添加一次。
asp.net页面生命周期及处理顺序:
PreInit(Page)--->Init(先Page后Control,Control根据架构自下而上,先子控件后父控件)--->LoadViewState(Control,先父控件后子控件)--->ProcessPostBackData(Control)--->Load(先Page后Control,Control根据架构自上而下,先父控件后子控件)--->RaisePostBackChangedEvent--->RaisePostBackEvent--->PreRender(先Page后Control,Control根据架构自上而下,先父控件后子控件)--->SaveViewState(先Page后Control,Control根据架构自上而下,先父控件后子控件)--->Render(先Page后Control,Control根据架构自上而下,先父控件后子控件)--->UnLoad/Dispose(先Control后Page,Control根据架构自下而上,先子控件后父控件)
Listing 1: Page Events Summary
| ||
Method | PostBack | Controls |
| ||
Constructor | Always | All |
AddParsedSubObject | Always | All |
DeterminePostBackMode | Always | Page |
OnInit | Always | All |
| ||
LoadPageStateFromPersistenceMedium | PostBack | Page |
LoadViewState | PostBack | All |
ProcessPostData1 | PostBack | Page |
OnLoad | Always | All |
| ||
ProcessPostData2 | PostBack | Page |
RaiseChangedEvents | PostBack | Page |
RaisePostBackEvent | PostBack | Page |
OnPreRender | Always | All |
| ||
SaveViewState | Always | All |
SavePageStateToPersistenceMedium | Always | Page |
Render | Always | All |
OnUnload | Always | All |
|
Initialization
1、在构造方法中,可以获得QueryString,Form,Cookie和Cache但是无法获得Session。
2、AddParsedSubObject方法是把页面上的各个控件添加到页面的控件树中,该方法可以被重构用来将控件添加到特殊的页面模版上,该方法会被子控件递归调用,这时从最底层的子控件开始初始化。
3、DeterminePostBackMode方法可以影响Page的IsPostBack属性,从而影响由IsPostBack属性决定的相应的事件。DeterminePostBackMode方法返回一个NameValueCollection对象,它是通过调用Page的GetCollectionBasedOnMethod方法来获得相应的NameValueCollection的。如果为null则IsPostBack=false,对于POST方式,会返回Request.Form的信息,对于Get方式,则返回Request.QueryString的信息。通过重构该方法来控制IsPostBack不是一个好的选择,因为会导致很多事件受到影响。
4、OnInit方法被调用时,所有在页面上定义的控件都已经初始化并且页面上的声明值也已经被赋给相应的属性,而ViewState和PostBack Data还没有被应用到控件上,这个方法最适合用于添加动态控件。
Restore and Load
5、LoadPageStateFromPersistenceMedium方法只有在PostBack(Page.IsPostBack=true)时才会被调用,重构该方法和SavePageStateToPersistenceMedium方法可以改变ViewState的存储方式,包括对ViewState的编码方式以及将ViewState存储在别的存储媒介中(如文件或者数据库中)而非页面的隐藏域里。该方法只是将ViewState从存储媒介中取出并进行相应的处理(如解码),而不是将ViewState应用到相应的控件中。
6、LoadViewState方法只有在PostBack时才会被调用,该方法是把ViewState应用到相应的控件中。该方法最适合用于根据之前手工存入ViewState中的值来恢复在事件中动态创建的控件。
7、ProcessPostData方法只有在PostBack时才会被调用,并且该方法无法被重构,因为它是一个由Page基类实现的private方法。该方法用于将WebForm上的被Post的信息恢复到相应的控件中。动态控件如果想获得Post的信息则必须在该方法之前被创建。这个方法还会比较ViewState中的值与Post的值是否一致,如果不一致会记录下变化以便用于之后Changed Events的触发。第一次调用该方法是为了恢复页面上定义的控件的Post信息以及记录控件值的变化以触发之后的Changed Events。
8、OnLoad方法被触发时,所有的状态和值都已经恢复,可以通过Page.IsPostBack属性来避免重复设定状态和值。在这个方法创建动态控件,动态控件ViewState的变化会被记录并且Post的信息不会丢失,因为后面还会再调用一次ProcessPostData方法。
Raised Events
9、第二次调用ProcessPostData方法,这是为了恢复动态创建的控件的Post信息以及记录动态控件值的变化以触发之后的Changed Events。在第二次调用ProcessPostData方法之后再创建的动态控件,ViewState的变化会被记录但是Post的信息会丢失且Changed Events不会被触发。
10、RaiseChangedEvents方法只有在PostBack时才会被调用,它也是一个由Page基类实现的private方法,因而无法被重构。如果有多个Changed Events被触发,它们之间的先后顺序是无序的,即无法保证触发的顺序。
11、RaisePostBackEvent方法只有在PostBack时才会被调用,它也是一个由Page基类实现的private方法,因而无法被重构。它是真正提交Form的事件,如果有验证控件,验证控件的验证功能会在此时被触发。IE有个bug,就是Enter键也可以提交Form但不会触发这个事件。
12、OnPreRender方法是最后一个可以影响页面的方法,它可以捕捉到上面所提的因为IE的一个bug而被触发的PostBack并加以处理。
Save and Render
13、SaveViewState方法会递归保存所有控件及其子控件的ViewState。
14、SavePageStateToPersistenceMedium用于将保存的ViewState编码并保存到存储媒介中,该方法和LoadPageStateFromPersistenceMedium可以一起被重构将ViewState保存到不同的存储媒介中,但ASP.Net要求必须将一个叫"__VIEWSTATE"的HiddenField传送出来即使是空的,所以如果要存储到其他媒介,这个需要注意。
15、Render方法会将每一个控件及其子控件转换成Html并发送给浏览器,可以在这个方法里手工加入页面头和脚,但是必须用Html来做,可以用StringBuilder、StringWriter和HtmlTextWriter来获取Html输出。
16、OnUnload方法会调用Dispose方法,这个方法提供了清理非托管资源的机会例如任何打开的文件或者数据库连接。这个方法被调用时,页面已经发送到了浏览器,所以它只能影响一些服务器端的对象,而且也不会在页面的Trace中出现。
Server.Transfer和Response.Redirect的区别
1、Server.Transfer方法只发生于服务器端的处理,不涉及客户端,而Response.Redirect方法会触发客户端,让客户端发送一个新的请求。
2、Server.Transfer方法只能够跳转到同一个应用下的指定类型页面(如aspx页面),而Response.Redirect方法没有这种限制。
3、Server.Transfer方法不会改变浏览器地址栏的URL(因为根本就不涉及客户端,不过由于URL不会改变,所以当用户在客户端手动刷新当前页面时会产生意想不到的问题),而Response.Redirect会改变。
4、Server.Transfer方法可以保留上一个页面的页面信息(例如隐藏域中保存的信息,TextBox中的信息等,在新页面用Request.Form来获取相应的值。还可以获取HttpContext.Items中保存的数据,因为是同一个请求。不过,是否保留上一个页面的页面信息需要进行设置,在调用Server.Transfer方法时要将第二个参数设为true,默认为false,该参数用于控制是否要保留上一个页面的页面信息),而Response.Redirect不会(调用Response.Redirect方法时一般情况下需要将第二个参数设为false,默认为true。因为第二个参数控制着是否需要调用Resposne.End方法,设为false则不会调用,这样可以继续执行Reponse.Redirect方法后的代码及流程)。
5、Server.Transfer方法能够传递的数据大小没有限制,而Response.Redirect有,一般为2KB。
6、Server.Transfer方法(Server.Execute也一样)返回的页面可能包含多个Body和Html标记,在不同的浏览器上会出现问题,而Response.Redirect不会。
Server.Execute和Server.Transfer的区别
1、Server.Execute在转向另一个页面并处理完该页面后,还会回到原来的页面继续处理,即page1-->page2-->page1,而Server.Transfer不会,即page1-->page2。
Page.IsPostBack
用来判断对当前Form的请求是第一次还是非第一次,非第一次就是PostBack,此时该属性为true。
IsPostBack为false的情况:
1、Server.Transfer/Server.Execute转页时,转到的页面的IsPostBack。
2、Post方式如果Request.Form==null;Get方式如果Request.QueryString==null
3、Request.Form/QueryString有请求值,但没有Key为"__VIEWSTATE", "__EVENTTARGET", "__VIEWSTATEFIELDCOUNT"的键值对, 并且没有Key为null且值以"__VIEWSTATE"开头和值为"__EVENTTARGET"的键值对。
4、Response.Redirect转到的页面。
5、在CrossPagePostBack时,对于目标页面,Page.IsPostBack。
6、Page运行时,对应的DLL发生变动或者Page的树结构发生变动,Page.IsPostBack。
IsPostBack为true的情况:
1、在CrossPagePostBack时,访问PreviousPage,PreviousPage.IsPostBack。
asp.net的定时处理实现方法
利用Cache的过期机制来实现。如果是整个应用则在Application_Start时设置一个Cache,过期时间即是定时的时间,然后在CacheItemRemovedCallBack事件处理时做需要做的事,然后再重新插入过期的Cache,重新设置时间。
跨页传送(CrossPagePostBack)
CrossPagePostBack是asp.net 2.0的一个新特性,用法就是在目标页面上添加诸如<%@ PreviousPageType VirtualPath="~/Source.aspx" %>,然后在源页面上将需要传送的值设置成页面的一个属性,并且添加一个可以触发PostBack的控件并且将控件的PostBackUrl属性设置成目标页面的URL,在目标页面上使用if (PreviousPage!=null && PreviousPage.IsCrossPagePostBack)来确定CrossPagePostBack,并且使用PreviousPage.Property1来获得源页面传送的值。
Page.Validate和Page.IsValid
Page.Validate方法是用于对需要验证的控件进行验证,这是因为asp.net自带的验证控件当客户端禁用javascript之后便会失效,为了防止在验证控件失效的情况下被人用恶意数据攻击,需要显式调用Page.Validate方法对需要验证的控件进行验证,该方法在任何CauseValidation=true的控件被触发PostBackEvent(不是Changed Event)时会被自动调用。asp.net 2.0可以使用Page.Validate(string)方法(该方法可以被重构)来验证一个验证组,如果调用Page.Validate方法则验证所有控件。该方法的工作原理就是循环访问Page.Validators中的验证控件并进行验证,Page.Validators是一个ValidatorCollection对象。IsValid属性就是验证完后,如果成功返回true,失败返回false。