Asp.net中的服务器控件都启用了viewstate,虽然方便了开发人员,但页面大小及性能上确实有所影响,对于无需viewstate的控 件及页面可以直接把控件或页面的viewstate禁用掉,但对于必须的viewstate我们可以采取另一种方式来处理——压缩。判断是否需要 viewstate压缩准则是,内网系统不必考虑压缩(网速够快还闲着没事消耗服务器CPU资源干啥啊。。。),对外系统根据viewstate长度选择 性进行压缩(当viewstate长度大于某个值时进行压缩,太短还压缩得不偿失哦。。。)
好了,下面具体展开吧!
一,viewstate压缩:
页面的生存周期里保存viewstate的方法为SavePageStateToPersistenceMedium方法,所以重写这个方法就ok了。
protected override void SavePageStateToPersistenceMedium(object state)
{
StringBuilder sb = new StringBuilder();
TextWriter tw = new StringWriter(sb);
LosFormatter lf = new LosFormatter();
lf.Serialize(tw, state);
tw.Flush();
tw.Close();
tw.Dispose();
bool useZip = false;
string finalStr = sb.ToString();
if (sb.Length >= 1096)
{
useZip = true;
byte[] bytes = Compress(Convert.FromBase64String(finalStr));
finalStr = Convert.ToBase64String(bytes);
}
ClientScript.RegisterHiddenField("_MyViewState", finalStr);
ClientScript.RegisterHiddenField("_UseZip", (useZip?"T":"F"));
}
private byte[] Compress(byte[] data)
{
MemoryStream ms = new MemoryStream();
GZipStream gzip = new GZipStream(ms, CompressionMode.Compress);
gzip.Write(data, 0, data.Length);
gzip.Flush();
gzip.Close();
gzip.Dispose();
byte[] bytes = ms.ToArray();
ms.Close();
ms.Dispose();
return bytes;
}
1.传入的参数state就是页面所有的viewstate集合对象,默认时该对象是经过序列化后保存到__ViewState的隐藏控件中的。所以要用LosFormatter对象将将state对象序列化,此时得到的序列化字符串是68位的数为基来编码的。
2.1096表示当viewstate长度大于1096时就压缩,否则就原封不动。
3..net有的System.IO.Compression中有两种压缩方式,这里选用GZip,也可以用其他更好的压缩算法的方法,压出效果就好^_^!
4.压缩完后恢复成以68位的数为基的编码字符串保存到__MyViewState的隐藏控件中,注意不能用回默认的__ViewState保存,否则会出错。
搞定压缩部分,当然都解压部分啦,回传时通过LoadPageStateFromPersistenceMedium方法获取viewstate,所以继续重写吧
protected override object LoadPageStateFromPersistenceMedium()
{
string myViewState = Request.Form["_MyViewState"];
bool useZip = (Request.Form["_UseZip"].Equals("T")?true:false);
LosFormatter lf = new LosFormatter();
if (useZip)
{
byte[] bytes = Convert.FromBase64String(myViewState);
bytes = Decompress(bytes);
return lf.Deserialize(Convert.ToBase64String(bytes));
}
else
{
return lf.Deserialize(myViewState);
}
}
private byte[] Decompress(byte[] data)
{
MemoryStream ms = new MemoryStream(data);
GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress);
byte[] resultByte = null;
int count = 1;
MemoryStream resultMs = new MemoryStream();
while (count >= 1)
{
resultByte = new byte[1024];
count = gzip.Read(resultByte, 0, 1024);
resultMs.Write(resultByte, 0, count);
}
resultByte = resultMs.ToArray();
resultMs.Close();
resultMs.Dispose();
gzip.Close();
gzip.Dispose();
ms.Close();
ms.Dispose();
return resultByte;
}
1.从__MyViewState中获取viewstate字符串,然后是解压、反序列,得到之前保存的ViewState对象。
这样ViewState的压缩就KO了。当然Asp.net还可以将ViewState保存到Session里面,设置一下就好了,非常方便,也免得自己来处理多页面出现的ViewState覆盖问题。
二,后置ViewState(2011.12.12校正)
viewstate默认是保存到页面的开头部分,如果长度过大会对搜索引擎爬该网站有一定的影响,可以通过把ViewState放置到页面最后的方式优化,后置viewstate还有一个好处就是页面会出来快一点哦。
protected override void Render(HtmlTextWriter writer)
{
StringWriter sw = new StringWriter();
HtmlTextWriter htw = new HtmlTextWriter(sw);
base.Render(htw);
htw.Flush();
htw.Close();
htw.Dispose();
StringBuilder resulteHtml = new StringBuilder(sw.ToString());
sw.Close();
sw.Dispose();
Regex reg = new Regex("<input type=\"hidden\" name=\"_MyViewState\" id=\"_MyViewState\" .* />");
string myViewState = reg.Match(resulteHtml.ToString()).Value;
if(!string.IsNullOrEmpty(myViewState))
{
resulteHtml.Replace(myViewState, string.Empty);
resulteHtml.Append(myViewState);//不能把自定义隐藏控件放在页面的最后
int formEndTag_index=resulteHtml.ToString().IndexOf("</form>");
resulteHtml.Insert(forEndTag_index,myViewState);
reg = new Regex("<input type=\"hidden\" name=\"_UseZip\" id=\"_UseZip\" .* />");
myViewState = reg.Match(resulteHtml.ToString()).Value;
resulteHtml.Replace(myViewState, string.Empty);
resulteHtml.Append(myViewState);//不能把自定义隐藏控件放在页面的最后
formEndTag_index=resulteHtml.ToString().IndexOf("</form>");
resulteHtml.Insert(forEndTag_index,myViewState);
}
Response.Write(resulteHtml.ToString());
}
Render是页面发送给用户前最后留给我们发挥的地方了(除了自定义HttpModule啦),base.Render()会将页面控件所生成的html代码输入到HtmlTextWriter
对象中,通过它就可以得到页面最终的html代码了,接着就用正则表达式获取viewstate部分,并移动到html代码的最后,然后直接输出到响应流中,至于重写方
法中的参数,就当作路人甲乙丙吧。
2011.12.12校正部分:
1.若直接把自定义隐藏控件保存到html代码末尾结果为:....</form><input type="hidden" id="_MyViewState" name="_MyViewState"... ,
当postback时在LoadPageStateFromPersistenceMedium时因隐藏控件在form之外,所以无法用form["参数命"]来获取。因此要将自定义隐藏控件放在</form
>标签之前。
2.判断myViewState是否为空字符串目的是,当使用ajax.net时render所得到的内容并不包含之前自定义的隐藏控件,防止string.replace中oldvalue为空时抛出异常。
3.当页面使用ajax.net时并且使用该基类对viewstate作处理,会出现UpdatePanel控件内的更新、删除操作失效,原因暂时不清楚,大家有没有好方法啊??请告诉我吧!~~