ViewState 到底是什么?ViewState 用于维护页面的 UI 状态。Web 是没有状态的,ASP.NET 页面也没有状态,它们在到服务器的每个往返过程中被实例化、执行、呈现和处理。在 ASP.NET 之前,通过多次回传将值恢复到窗体字段中完全是页面开发人员的责任,他们将不得不从 HTTP 窗体中逐个拾取回传值,然后再将其推回字段中。幸运的是,现在 ASP.NET 可以自动完成这项任务,从而为开发人员免除了一项令人厌烦的工作,同时也无需再为窗体编写大量的代码。但这并不是 ViewState。
ViewState是一种机制,ASP.NET 使用这种机制来跟踪服务器控件状态值,否则这些值将不作为 HTTP 窗体的一部分而回传。例如,由 Label 控件显示的文本默认情况下就保存在 ViewState 中。作为开发人员,您可以绑定数据,或在首次加载该页面时仅对 Label 编程设置一次,在后续的回传中,该标签文本将自动从 ViewState 中重新填充。因此,除了可以减少繁琐的工作和代码外,ViewState 通常还可以减少数据库的往返次数。
ViewState 的工作原理
ViewState 确实没有什么神秘之处,它是由 ASP.NET 页面框架管理的一个隐藏的窗体字段。当 ASP.NET 执行某个页面时,该页面上的 ViewState 值和所有控件将被收集并格式化成一个编码字符串,然后被分配给隐藏窗体字段的值属性(即 <input type=hidden>)。由于隐藏窗体字段是发送到客户端的页面的一部分,所以 ViewState 值被临时存储在客户端的浏览器中。如果客户端选择将该页面回传给服务器,则 ViewState 字符串也将被回传。
关于 ViewState 还有三个值得注意的小问题
1、如果要使用 ViewState,则在 ASPX 页面中必须有一个服务器端窗体标记 (<form runat=server>)。窗体字段是必需的,这样包含 ViewState 信息的隐藏字段才能回传给服务器。而且,该窗体还必须是服务器端的窗体,这样在服务器上执行该页面时,ASP.NET 页面框架才能添加隐藏的字段。
2、页面本身将 20 字节左右的信息保存在 ViewState 中,用于在回传时将 PostBack 数据和 ViewState 值分发给正确的控件。因此,即使该页面或应用程序禁用了 ViewState,仍可以在 ViewState 中看到少量的剩余字节。
3、在页面不回传的情况下,可以通过省略服务器端的 <form> 标记来去除页面中的 ViewState。
设定ViewState的值
ViewState中部分值是可以通过代码的形式显示设置或调用的,使用的编程语法和高速缓存的语法类似。
//保存在ViewState中
ViewState["yourName"] = "Leopold";
//从 ViewState 中读取
string yourName = (string)ViewState["SortOrder"];
ViewState安全吗?
由于 ViewState 没有被格式化为清晰的文本,某些人有时会认为它被加密了,其实并没有。相反,ViewState 只是进行了 Base64 编码,以确保值在往返过程中不会发生变化,而并不考虑应用程序使用的响应/请求编码。
ViewState在Render事件前在Page.SavePageStateToPersistenceMedium方法中保存,
//ViewState Saved in Session State
protected override object LoadPageStateFromPersistenceMedium() {
return Session["ViewState"];
}
protected override void SavePageStateToPersistenceMedium(object viewState) {
Session["ViewState"] = viewState;
// Bug requires Hidden Form Field __VIEWSTATE
RegisterHiddenField("__VIEWSTATE", "");
}
并在PostBack时通过Page.LoadPageStateFromPerSistenceMedium方法恢复。
protected override object LoadPageStateFromPersistenceMedium() {
LosFormatter format = new LosFormatter();
return format.Deserialize(YourDataStore["ViewState"]);
}
protected override void SavePageStateToPersistenceMedium(object viewState) {
LosFormatter format = new LosFormatter();
StringWriter writer = new StringWriter();
format.Serialize(writer, viewState);
YourDataStore["ViewState"] = writer.ToString();
}
通过继承两个方法可以很容易地将ViewState保存到Session中,这在低带宽网络环境下可以减少网络流量。现在完全出于兴趣地来看下ViewState的内在结构。每个ViewState在Triplet(System.Web.UI.Triplet)中保存,其中第一个对象是System.Web.UI.Pair,第二个对象是一个以树目录形式保存子控件的数组,第三个对象是以数组保存的与那些子控件相关的Triplets。这些都很难用语言来解释清楚,还是看下例子吧。
//Encoded ViewState:
dDwxMjM0NTY3ODkwO3Q8cDxsPHBycEE7cHJwQjtwcnBDOz47bDx2YWxBO3ZhbEI7dmFsQzs+PjtsPGk8
MD47aTwyPjtpPDM+O2k8NT47PjtsPHQ8cDxsPHBycEE7cHJwQjs+O2w8dmFsQTt2YWxCOz4+Ozs+O3Q8
cDxsPHBycEE7cHJwQjs+O2w8dmFsQTt2YWxCOz4+Ozs+O3Q8cDxsPHBycEE7cHJwQjs+O2w8dmFsQTt2
YWxCOz4+Ozs+O3Q8cDxsPHBycEE7cHJwQjs+O2w8dmFsQTt2YWxCOz4+Ozs+Oz4+Oz4=
//Decoded ViewState:
t<1234567890;t<p<l<prpA;prpB;prpC;>;l<valA;valB;valC;>>;
l<i<0>;i<2>;i<3>;i<5>;>;l<
t<p<l<prpA;prpB;>;l<valA;valB;>>;;>;
t<p<l<prpA;prpB;>;l<valA;valB;>>;;>;
t<p<l<prpA;prpB;>;l<valA;valB;>>;;>;
t<p<l<prpA;prpB;>;l<valA;valB;>>;;>;>>;>
解析用代码:
protected override void SavePageStateToPersistenceMedium(object viewState) {
// Call Base Method to Not Change Normal Process
base.SavePageStateToPersistenceMedium(viewState);
// Retrieve ViewState and Write Out to Page
LosFormatter format = new LosFormatter();
StringWriter writer = new StringWriter();
format.Serialize(writer, viewState);
string vsRaw = writer.ToString();
Response.Write("ViewState Raw: " + Server.HtmlEncode(vsRaw));
// Decode ViewState and Write Out to Page
byte[] buffer = Convert.FromBase64String(vsRaw);
string vsText = Encoding.ASCII.GetString(buffer);
Response.Write("ViewState Text: " + Server.HtmlEncode(vsText));
// Parse ViewState -- Turn On Page Tracing
ParseViewState(viewState, 0);
}
private void ParseViewState(object vs, int level) {
if (vs == null) {
Trace.Warn(level.ToString(), Spaces(level) + "null");
}
else if (vs.GetType() == typeof(System.Web.UI.Triplet)) {
Trace.Warn(level.ToString(), Spaces(level) + "Triplet");
ParseViewState((Triplet) vs, level);
}
else if (vs.GetType() == typeof(System.Web.UI.Pair)) {
Trace.Warn(level.ToString(), Spaces(level) + "Pair");
ParseViewState((Pair) vs, level);
}
else if (vs.GetType() == typeof(System.Collections.ArrayList)) {
Trace.Warn(level.ToString(), Spaces(level) + "ArrayList");
ParseViewState((IEnumerable) vs, level);
}
else if (vs.GetType().IsArray) {
Trace.Warn(level.ToString(), Spaces(level) + "Array");
ParseViewState((IEnumerable) vs, level);
}
else if (vs.GetType() == typeof(System.String)) {
Trace.Warn(level.ToString(), Spaces(level) + "'" + vs.ToString() + "'");
}
else if (vs.GetType().IsPrimitive) {
Trace.Warn(level.ToString(), Spaces(level) + vs.ToString());
}
else {
Trace.Warn(level.ToString(), Spaces(level) + vs.GetType().ToString());
}
}
private void ParseViewState(Triplet vs, int level) {
ParseViewState(vs.First, level + 1);
ParseViewState(vs.Second, level + 1);
ParseViewState(vs.Third, level + 1);
}
private void ParseViewState(Pair vs, int level) {
ParseViewState(vs.First, level + 1);
ParseViewState(vs.Second, level + 1);
}
private void ParseViewState(IEnumerable vs, int level) {
foreach (object item in vs) {
ParseViewState(item, level + 1);
}
}
private string Spaces(int count) {
string spaces = "";
for (int index = 0; index < count; index++) {
spaces += " ";
}
return spaces;
}
对这些代码,还没有亲自验证过,资料来的已经比较早,好像Microsoft已经打过补丁了。不过网上还是能找到Decode ViewState的工具来解析ViewState。试了一下内部的站点,大多是解析失败的,但还是找到了一个现成的案例BS2Portal
BS2Portal,找到_ViewState
在工具中输入BS2Portal地址,Extract 一下,如果成功,ViewState String 中显示ISVIEW,然后再ViewState 输入_ViewState值,Decode一下就能看到ViewState 中的值了。
如何让ViewState更安全
可以向应用程序中添加两种 ViewState 安全级别:
防篡改
加密
防篡改
尽管散列代码不能确保 ViewState 字段中实际数据的安全,但它能够显著降低有人通过 ViewState 骗过应用程序的可能性,即防止回传应用程序通常禁止用户输入的值。
可以通过设置 EnableViewStateMAC 属性来指示 ASP.NET 向 ViewState 字段中追加一个散列代码:
%@Page EnableViewStateMAC=true %
可以在页面级别上设置 EnableViewStateMAC,也可以在应用程序级别上设置。在回传时,ASP.NET 将为 ViewState 数据生成一个散列代码,并将其与存储在回传值中的散列代码进行比较。如果两处的散列代码不匹配,该 ViewState 数据将被丢弃,同时控件将恢复为原来的设置。
默认情况下,ASP.NET 使用 SHA1 算法来生成 ViewState 散列代码。此外,也可以通过在 machine.config 文件中设置 <machineKey> 来选择 MD5 算法,如下所示:
<machineKey validation="MD5" />
加密
可以使用加密来保护 ViewState 字段中的实际数据值。首先,必须如上所述设置 EnableViewStatMAC="true"。然后,将 machineKey validation 类型设置为 3DES。这将指示 ASP.NET 使用 Triple DES 对称加密算法来加密 ViewState 值。
<machineKey validation="3DES" />
Web 领域中的 ViewState 安全性
默认情况下,ASP.NET 将创建一个随机的验证密钥,并存储在每个服务器的本地安全授权 (LSA) 中。要验证在另一台服务器上创建的 ViewState 字段,两台服务器的 validationKey 必须设置为相同的值。如果要通过上述方式之一,对运行于 Web 领域配置中的应用程序进行 ViewState 安全设置,则需要为所有服务器提供一个唯一的、共享的验证密钥。
验证密钥是一个包含 20 到 64 位密码增强字节的随机字符串,用 40 到 128 个十六进制字符表示。密钥越长越安全,因此建议使用 128 个字符的密钥(如果计算机支持)。例如:
<machineKey validation="SHA1" validationKey="
F3690E7A3143C185AB1089616A8B4D81FD55DD7A69EEAA3B32A6AE813ECEECD28DEA66A
23BEE42193729BD48595EBAFE2C2E765BE77E006330BC3B1392D7C73F" />
System.Security.Cryptography 名称空间包括 RNGCryptoServiceProvider 类,使用该类可以生成此字符串,如以下 GenerateCryptoKey.aspx 示例所示:
<%@ Page Language="c#" %>
<%@ Import Namespace="System.Security.Cryptography" %>
<HTML>
<body>
<form runat="server">
<H3>生成随机加密密钥</H3>
<P>
<asp:RadioButtonList id="RadioButtonList1"
runat="server" RepeatDirection="Horizontal">
<asp:ListItem Value="40">40-byte</asp:ListItem>
<asp:ListItem Value="128" Selected="True">128-byte</asp:ListItem>
</asp:RadioButtonList>
<asp:Button id="Button1" runat="server" οnclick="GenerateKey"
Text="生成密钥">
</asp:Button></P>
<P>
<asp:TextBox id="TextBox1" runat="server" TextMode="MultiLine"
Rows="10" Columns="70" BackColor="#EEEEEE" EnableViewState="False">
复制并粘贴生成的结果</asp:TextBox></P>
</form>
</body>
</HTML>
<script runat=server>
void GenerateKey(object sender, System.EventArgs e)
{
int keylength = Int32.Parse(RadioButtonList1.SelectedItem.Value);
// 在此处放入用于初始化页面的用户代码
byte[] buff = new Byte[keylength/2];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
// 该数组已使用密码增强的随机字节进行填充
rng.GetBytes(buff);
StringBuilder sb = new StringBuilder(keylength);
int i;
for (i = 0; i < buff.Length; i++) {
sb.Append(String.Format("{0:X2}",buff[i]));
}
// 粘贴到文本框,用户可从中复制
TextBox1.Text = sb.ToString();
}
</script>
欢迎讨论
参考文献 :
1 Susan Warren ‘ASP.NET ViewState 初探’ China MSDN 11/2001
2 Kaneboy's Blog ‘View State’ 15/16/2004
3 Paul Wilson 'ViewState: All You Wanted to Know' 11/2/2006