之所以用这个题目是因为很多人常常问我研究开源项目的意义,有一些开源项目在别人眼里看起来完全没有搞头,是一些没有实用价值的项目。其实开源项目往往是新技术的试验田,是全世界优秀程序开发者智慧的汇集,如果你仔细研读这些开源项目,得到的不仅仅是这个项目的功能,而是观摩新的技术,学习比你更聪明人的智慧。
我一直研究的一个开源项目是DotNetNuke(简称DNN),也许你不知道这是一个什么系统,不过你不用关心这是一个什么系统,因为我要讨论的技术在任何一个ASP.NET网站里都有可能用到,只是用这个开源项目做示范罢了,不过你会看到在开源项目里,MSDN里那些死板的技术说明是如何巧妙发挥功能。
开源网站系统提供的性能设置选项
在DNN开源项目里,有一些设置可以调校网站的性能,包括以下这几个:
我会花几篇文章逐个讨论一下这几个选项对网站性能的影响,并给出一些技术分析。 在这一篇文章里,我讨论Page State Persistence这个设置。
什么是Page Sate Persistence
我想大家对ViewState一定不陌生,ViewState是ASP.NET相对ASP新增的一个强大的功能,能够保存一个页面的state。简单的说,就是保存你提交页面时,文本框,下拉列表等等在页面提交那一时刻的值,这样万一你填写的内容通不过检验,就可以用这些信息来还原之前的状态(当然,用处不止这些)。知道ViewSate的人,一般都知道ViewSate使用一个hidden input来保存页面的state信息。网上有很多这样的文章,大家可以Google一下。那这个ViewSate跟性能又有什么关系呢?因为这个hidden input是要跟随PostBack信息在客户端和服务器之间来回传递的,如果这个值太大,自然会影响性能。当然,除了大小,还有一个方面就是ViewState存储的位置了,如果我们不来回传递ViewState的值,而是存储在服务器这一端,自然会减少ViewState在网络上传输占用的时间,但同时却加重了服务器的负担。
可能很多人不知道ASP.NET 2.0的一个新特性就是可以通过重载PageStatePersister类从而实现自定义page ViewState的存储位置。在ASP.NET v1.x里,ViewState只能是存储在之前提到的那个隐藏input元素中。在ASP.NET 2.0 中,新增的SessionPageStatePersister类就提供了把ViewState存储在session里的功能。
开源项目DNN构架如何利用这一新特性
这个开源项目重写了页面的基类,在它自己的基类中,有一个PageStatePersister只读属性,这个属性会根据用户的设置,返回一个System.Web.UI.PageStatePersister实体,之后运行的页面就会用这个实体去实现ViewState的存储,从而实现由用户选择在哪里存储ViewState,从而调节性能。
代码如下:
Protected Overrides ReadOnly Property PageStatePersister() As System.Web.UI.PageStatePersister
Get
'Set ViewState Persister to default (as defined in Base Class)
Dim _persister As PageStatePersister = MyBase.PageStatePersister
If Not DotNetNuke.Common.Globals.HostSettings("PageStatePersister") Is Nothing Then
Select Case DirectCast(DotNetNuke.Common.Globals.HostSettings("PageStatePersister"), String)
Case "M"
_persister = New CachePageStatePersister(Me)
Case "D"
_persister = New DiskPageStatePersister(Me)
Case "S"
_persister = New SessionPageStatePersister(Me)
End Select
End If
Return _persister
End Get
End Property
从代码我们可以看到,如果用户选择“Page”,那就使用默认的hidden input元素在页面上存储viewstate(在代码的处理上,是判断其它非选择“Page”项的情况,所以你找不到 Case "P" 这一句);如果用户选用的是"Memory",那么在Case "M"这一句,就会返回一个CachePageStatePersister,这是这个开源系统自定义的一个viewstate处理类。那么DNN开源系统就会使用Cache在服务器端存储viewstate的信息,也就是存储在服务器的“Memory”中。
如果是"D",那么是存储在服务端的文件里,所以是"Disk",但系统似乎保留了这个设置,没有提供界面。
如果是"S",那么是存储在Session里面。这个实现是ASP.NET 2.0 已经提供了的。
如何实现
其实比较简单,在ASP.NET 2.0 里, 抽象类PageStatePersister 实现了存储page state到其它地方的基本功能,你只要重写这个抽象类提供的Load和Save方法就可以了。
我们看看DNN开源项目实现的一个CachePageStatePersister:
Imports System.Text
Namespace DotNetNuke.Framework
''' -----------------------------------------------------------------------------
''' Namespace: DotNetNuke.Framework
''' Project: DotNetNuke
''' Class: CachePageStatePersister
''' -----------------------------------------------------------------------------
''' <summary>
''' CachePageStatePersister provides a cache based page state peristence mechanism
''' </summary>
''' <history>
''' [cnurse] 11/30/2006 documented
''' </history>
''' -----------------------------------------------------------------------------
Public Class CachePageStatePersister
Inherits PageStatePersister
Private Const VIEW_STATE_CACHEKEY As String = "__VIEWSTATE_CACHEKEY"
''' -----------------------------------------------------------------------------
''' <summary>
''' Creates the CachePageStatePersister
''' </summary>
''' <history>
''' [cnurse] 11/30/2006 Documented
''' </history>
''' -----------------------------------------------------------------------------
Public Sub New(ByVal page As Page)
MyBase.New(page)
End Sub
''' -----------------------------------------------------------------------------
''' <summary>
''' Loads the Page State from the Cache
''' </summary>
''' <history>
''' [cnurse] 11/30/2006 Documented
''' </history>
''' -----------------------------------------------------------------------------
Public Overrides Sub Load()
' Get the cache key from the web form data
Dim key As String = TryCast(Page.Request.Params(VIEW_STATE_CACHEKEY), String)
'Abort if cache key is not available or valid
If String.IsNullOrEmpty(key) Or Not key.StartsWith("VS_") Then
Throw New ApplicationException("Missing valid " + VIEW_STATE_CACHEKEY)
End If
Dim state As Pair = TryCast(DataCache.GetPersistentCacheItem(key, GetType(Pair)), Pair)
If Not state Is Nothing Then
'Set view state and control state
ViewState = state.First
ControlState = state.Second
End If
'Remove this ViewState from the cache as it has served its purpose
DataCache.RemovePersistentCacheItem(key)
End Sub
''' -----------------------------------------------------------------------------
''' <summary>
''' Saves the Page State to the Cache
''' </summary>
''' <history>
''' [cnurse] 11/30/2006 Documented
''' </history>
''' -----------------------------------------------------------------------------
Public Overrides Sub Save()
'No processing needed if no states available
If ViewState Is Nothing And ControlState Is Nothing Then
Exit Sub
End If
'Generate a unique cache key
Dim key As New StringBuilder()
With key
.Append("VS_")
.Append(IIf(Page.Session Is Nothing, Guid.NewGuid().ToString(), Page.Session.SessionID))
.Append("_")
.Append(DateTime.Now.Ticks.ToString())
End With
'Save view state and control state separately
Dim state As New Pair(ViewState, ControlState)
'Add view state and control state to cache
DataCache.SetCache(key.ToString(), state, Nothing, DateTime.Now.AddMinutes(Page.Session.Timeout), System.Web.Caching.Cache.NoSlidingExpiration, True)
'Register hidden field to store cache key in
Page.ClientScript.RegisterHiddenField(VIEW_STATE_CACHEKEY, key.ToString())
End Sub
End Class
End Namespace
对性能有什么影响
性能永远是一个博弈的问题,没有绝对,只有如何平衡。把viewstate放在page的hidden input里面自然会增加客户端的处理时间以及网路传输时间。把viewstate放在服务器端自然会消耗服务器的内存(硬盘)和CPU,所以,没有最好的设置,只有最合适的设置。
- 如果使用专属的服务器,并且配置较高,使用"Memory"模式,这样会提高网站的速度。
- 对于一般的应用,使用默认的viewstate设置就可以了,也就是使用hidden input,针对这个开源系统,就是选择"Page"。
- 如果你发现有一些页面的viewstate特别大,消耗了大量的网络传输时间,你可以选用memory模式,把viewstate放在服务端的Cache里。
- 很明显,如果使用Disk的方式,会比使用Cache的慢,这种方式也许可以用在那些对反映速度要求不高,viewstate又很大的页面上。
- 对于存储在Session里的实现方式,之前放在hidden INPUT里的信息会通过BASE64编码在Session里面传递,估计性能上比默认直接使用hidden input的要慢,毕竟你还要进行编码的转换,不过可能对安全性上有帮助,只是我的推测。希望有高手指出错误.
参考文档:
ScottGu's Blog: PageStatePersister Extensibility with ASP.NET 2.0
PageStatePersister class in 2.0 - To persist state on page or Session or even on Database.