前言
只要对ViewState稍有了解,就会知道,Asp.net页面中ViewState一般是存储在页面的一个隐藏域中:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="一堆乱七八糟的东西">
当我们浏览页面源文件时,看到的那一大堆(特别是当页面有个有大量数据的DataGrid,或在ASP.NET2.0中的GridView时)乱七八糟的东西的时候,那就是ViewState了。
基础知识
因为,在ASP.NET2.0中ViewState的持久性存储机制有了些新的变化,所以,还是简单介绍下相关的东西。
在ASP.NET1.1中,只提供了页面隐藏域的持久性机制,这样在某些情况下不得不放弃使用ViewState,试想下,如果你的DataGrid中有上万条记录(别认为这种变态的需要是没有的,有人就碰到过),如果启用了ViewState,你感保证你的IIS服务器能承受得住吗,网络承受得主吗?当然你是可以通过重写Page.SavePageStateToPersistenceMedium()方法来更改你的存储机制,但别忘了重写Page.LoadPageStateFromPersistenceMedium(),它们可是一对的啊。
ASP.NET2.0 中的默认视图状态持久性机制依然是在页上的一个隐藏 HTML 元素(一个 type 属性设置为 "hidden" 的元素)中将状态信息保留为一个 Base64 编码的字符串。ASP.NET 页使用 HiddenFieldPageStatePersister 对象执行此项工作,并使用一个 IStateFormatter 实例对对象状态信息进行序列化和反序列化。或者,对于带宽和资源有限的移动客户端,您也可以使用 SessionPageStatePersister 类在服务器上的 Session 对象中存储页的视图状态,其实也就多了个Session持久机制而已,让我们把页面状态保存在Session中,而不是页面中,这对带宽是一种节省。
但你要深入的了解ViewState持久机制的话,抽象类PageStatePersister你是应该去了解的,要在不能支持现有视图状态持久性机制的客户端上保留视图状态,可以扩展 PageStatePersister 类,引入您自己的视图状态持久性方法,并且可以使用页适配器将 ASP.NET 应用程序配置为根据为其提供页的客户端的类型使用不同的视图状态持久性机制。从 PageStatePersister 类派生的类必须重写 Save 抽象方法,以便在持久性介质中存储视图状态和控件状态,同时重写 Load 方法以提取状态信息。如果需要将视图状态和控件状态序列化为字符串,可以使用通过 StateFormatter 属性来访问的 IStateFormatter 对象。它可以高效地将对象状态信息序列化和反序列化为 Base64 编码字符串。还可以重写 StateFormatter 属性以提供自己的对象状态序列化机制,如何为之,我的代码中都有介绍,很简单,看看就明白了。
ViewState持久性机制
隐藏域
这个就不介绍了,默认的就是这种。就入前言中的那样。
Session
在ASP.NET2.0中只要重写PageStatePersister属性就可以了。
protected override PageStatePersister PageStatePersister
{
get
{
return new SessionPageStatePersister(Page);
}
}
要是在ASP.NET1.1中需要重写LoadPageStateFromPersistenceMedium这两个方法:
protected override object LoadPageStateFromPersistenceMedium()
{
return Session["ViewState"];
}
protected override void SavePageStateToPersistenceMedium(object viewState)
{
Session["ViewState"] = viewState;
RegisterHiddenField("__VIEWSTATE", "");
}
数据库(我的示例是SQL Server2000)
在ASP1.1中,请注意下面紫色的那行,我也不太清楚那有什么用,它让我郁闷了好几天,等下你就明白我的郁闷了。还有下面的代码只是凑我的源码中拷贝出来的,你完全可以不这样写的,除了那些必要的外。
protected override void SavePageStateToPersistenceMedium(object state)
{
string viewStateID = "VIEWSTATE#" + Session.SessionID.ToString() + "#" + DateTime.Now.Ticks.ToString();
ClientScript.RegisterHiddenField("__VIEWSTATE_KEY", viewStateID);
ClientScript.RegisterHiddenField("__VIEWSTATE","");//请注意
try
{
if (losFormatter == null)
{
losFormatter = new LosFormatter();
}
StringWriter sw = new StringWriter();
losFormatter.Serialize(sw, state);
Common.ViewStateData vsd = new ViewStateData();
vsd.ViewStateID = viewStateID;
vsd.ViewState = sw.ToString();
da = new DataAccess();
string error = da.SaveViewState(vsd);
Response.Write(error);
}
catch (Exception ex)
{
Response.Write(ex.Message);
}
}
protected override object LoadPageStateFromPersistenceMedium()
{
string viewState = string.Empty;
try
{
if (losFormatter == null)
{
losFormatter = new LosFormatter();
}
string stateID = Page.Request["__VIEWSTATE_KEY"].ToString();
da = new DataAccess();
viewState = da.LoadViewState(stateID);
}
catch
{}
return losFormatter.Deserialize(viewState);
}
在ASP2.0中这行代码基本是可以的,为什么是基本呢,因为就是上面那行 ClientScript.RegisterHiddenField("__VIEWSTATE","");
有没有这行,在Asp.net1.1中都是可行的,我也是参考过别人的代码,这行就这么加入了,加了这行后,只是在页面中多了个
<input type="hidden" name="__VIEWSTATE" value="" />
也就是运行后页面的源文件中有两个这样的东西。去掉那行也可以,所以我不明白语句是用做什么的,请明白的告诉我吧。但是在Asp.net2.0中就不行,有如下错误:
The state information is invalid for this page and might be corrupted.
反正当时就是晕晕的,我以前从来没有碰到过如是错误,去google也无所得,是啊,打死我也不知道是那句错了啊,就这么郁闷了两天,问题无法解决,本人天生愚钝的,我跟踪视图状态的存入数据库与从数据库的读取的整个过程,硬是找不到错误,我就反复思考这些代码,惟有那行,我就是有点迷惑,为什么还要页面注册一个“__VIEWSTATE”的隐藏域呢,于是我就注释掉这行,居然可以运行了,所以我还是不明白那行是什么用意。
当然我们还可以通过写一个PageStatePersister新子类也可以完成上述功能,这是ASP.NET2.0新增的:
namespace PageAdapter
{
using System;
using System.IO;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
public class DatabasePageStatePersister : PageStatePersister
{
public DatabasePageStatePersister(Page page): base(page)
{}
//
// Load ViewState and ControlState.
//
public override void Load()
{
string viewState;
IStateFormatter formatter = this.StateFormatter;
DataAccess da = new DataAccess();
string stateID = base.Page.Request["__VIEWSTATE_KEY"].ToString();
viewState = da.LoadViewState(stateID);
Pair statePair = (Pair)formatter.Deserialize(viewState);
ViewState = statePair.First;
ControlState = statePair.Second;
}
//
// Persist any ViewState and ControlState.
//
public override void Save()
{
if (ViewState != null || ControlState != null)
{
if (Page.Session != null)
{
string viewStateID = "VIEWSTATE#" + base.Page.Session.SessionID.ToString() + "#" + DateTime.Now.Ticks.ToString();
base.Page.ClientScript.RegisterHiddenField("__VIEWSTATE_KEY", viewStateID);
Pair statePair = new Pair(ViewState, ControlState);
IStateFormatter formatter = this.StateFormatter;
// Serialize the statePair object to a string.
string serializedState = formatter.Serialize(statePair);
ViewStateData vsd = new ViewStateData();
vsd.ViewStateID = viewStateID;
vsd.ViewState = serializedState;
DataAccess da = new DataAccess();
string error = da.SaveViewState(vsd);
}
else
throw new InvalidOperationException("Session needed for StreamPageStatePersister.");
}
}
}
}
再有重写PageStatePersister属性就可以了:
protected override PageStatePersister PageStatePersister
{
get
{
return new DatabasePageStatePersister(Page);
}
文件
这其实也跟数据库的差不了多少,我这只讲ASP.NET2.0的,在ASP.NET1.1也应该差不多,但我没有写代码调试:
还是用那种写PageStatePersister新子类的办法:
namespace StreamPageAdapter
{
using System;
using System.IO;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
//
// The StreamPageStatePersister is an example view state
// persistence mechanism that persists view and control
// state on the Web server.
//
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
public class StreamPageStatePersister : PageStatePersister
{
public StreamPageStatePersister(Page page): base(page)
{}
//
// Load ViewState and ControlState.
//
public override void Load()
{
Stream stateStream = GetSecureStream();
// Read the state string, using the StateFormatter.
StreamReader reader = new StreamReader(stateStream);
IStateFormatter formatter = this.StateFormatter;
string fileContents = reader.ReadToEnd();
// Deserilize returns the Pair object that is serialized in
// the Save method.
Pair statePair = (Pair)formatter.Deserialize(fileContents);
ViewState = statePair.First;
ControlState = statePair.Second;
reader.Close();
stateStream.Close();
}
//
// Persist any ViewState and ControlState.
//
public override void Save()
{
if (ViewState != null || ControlState != null)
{
if (Page.Session != null)
{
Stream stateStream = GetSecureStream();
StreamWriter writer = new StreamWriter(stateStream);
IStateFormatter formatter = this.StateFormatter;
Pair statePair = new Pair(ViewState, ControlState);
// Serialize the statePair object to a string.
string serializedState = formatter.Serialize(statePair);
writer.Write(serializedState);
writer.Close();
stateStream.Close();
}
else
throw new InvalidOperationException("Session needed for StreamPageStatePersister.");
}
}
// Return a secure Stream for your environment.
private Stream GetSecureStream()
{
string path = @"d:a.txt";
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
return fs;
}
}
}
再重写PageStatePersister属性就可以了:
protected override PageStatePersister PageStatePersister
{
get
{
return new StreamPageStatePersister (Page);
}
通过上面的简单介绍,我们应该有所了解了,只是要明白的是:在ASP.NET1.1中我们只能通过重写age.SavePageStateToPersistenceMedium()和Page.LoadPageStateFromPersistenceMedium()来完成上述功能;而在ASP.NET2.0中,我们除了这外,还和通过写PageStatePersister新子类和重写PageStatePersister属性来完成,我是没有发现什么不同,当然如果在下面的内容你就明白,写PageStatePersister新子类的真正用处了。
使用页适配器
由于状态持久性机制与自适应呈现和客户端功能有关,因此提供MyPageAdapter激活 ASP.NET 应用程序的 DatabasePageStatePersister。最后,提供了一个浏览器功能 (.browser) 文件来为特定类别的客户端(在此例中为默认 Web 浏览器)启用 MyPageAdapter器。
这些内容请具体看我提供的源码中的PageAdapter工程。看了就明白了。
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
namespace PageAdapter
{
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
public class MyPageAdapter : System.Web.UI.Adapters.PageAdapter
{
public override PageStatePersister GetStatePersister()
{
return new PageAdapter.DatabasePageStatePersister(Page);
}
}
}
最后,为了启用 MyPageAdapter 适配器,您必须在 ASP.NET 应用程序的根目录下创建一个名为 App_Browsers 的目录,并在其中包括一个包含配置信息的 .browser 文件(其实这些都在你向工程中添加一个.browser文件时vs2005会自动给你完成的。配置文件中的 <refID元素指示该配置重写为 Default.browser 配置文件中的默认浏览器指定的值。此示例将 MyPageAdapter 用于 ASP.NET 网页(但通常不使用适配器)。
<browsers>
<browser refID="Default" >
<controlAdapters>
<adapter
controlType="System.Web.UI.Page"
adapterType="PageAdapter.MyPageAdapter" />
</controlAdapters>
</browser>
</browsers>
这可以看源码中的TestPageAdapter工程。该工程用来演示页适配器的。
结束语
说得比较简单,可能也不是很明白,至于各种持久机制的优劣,我也没有专门测试过,而且最后一条“使用页适配器”也不是属于持久机制,只是用了也适配器,我们就不要重写
PageStatePersister属性了,我看来好像用处不是很大,因为我们可以把重写PageStatePersister的动作放在页面基类中,其它所有页面都继承这个基类就可以了,在我代码中就是这么做的,用这个页适配器还麻烦了,当然我也并太清楚页适配器的这个东东。
另外,对我的源码做个简单说明:
1. PageAdapter工程
DatabasePageStatePersister.cs:PageStatePersister类的子类
MyPageAdapter.cs:页适配器
DataAccess.cs和ViewSate.cs数据库访问的,属于辅助类。
2. StreamPageAdapter工程
这个与上面的相似,不多说了
3. SaveStateToDatabase工程
StateInHiddenField.aspx:测试默认的存储机制,就是在看页面源文件时可以看到一大堆乱七八糟东西的。
StateInSession.aspx:存储机制为Session
StateInDatabase.aspx:存储机制数据库,是重写方法的那种,asp.net1.1,2.0都可以用的。
StateInDatabase2.aspx:写PageStatePersister新子类的并重写PageStatePersister属性的那种
StateInFile.aspx:把ViewState保存在服务器中某个文件夹中。
4. TestPageAdater工程。
用来测试也适配器用的。