在 asp.net里面一般的生命周期都比较短,如果想要比较长久的保存数据的话,一般有选择几种方式可供选择,即 cookies、 ViewState、 Session、 Cache、 application等。他们各有优缺点,也各有其自己的使用范围。
我现在遇到了两个问题,第一个是如何在这几种方式里面快速、方便的切换,第二个是如何实现一个既可以区分用户,又可以区分页面,又节省服务器的资源,又比较安全的保存数据的方式。
ViewState比较符合第二个问题的要求,但是他不太安全,表面上看他存放在客户端的是乱码,其实是可以解密的,解密之后就是明文了,你存放的是什么就一目了然。如果是使用 ViewState保存一般的数据倒也是没有什么问题,但是我想保存的是表名、字段名、 SQL语句这样的很敏感的数据,这样的数据放在 ViewState里面,估计会被人骂死,呵呵。
以前的 QuickPager分页控件确实是这么处理的,现在越想越不安全,自己用用也就凑合了,如果推广的话,那就害人了。所以我不得不想办法来解决这个很严重的问题。于是我想写一个独立的能够保存数据的类。这个类可以使用各种方式来存放数据,如果要加密数据的话,也可以自己设置密钥,这样不知道密钥的话,就不能解密了(除非暴力破解),当然您也可以选择不加密(保存在 Session、 Cache就不用加密了),也可以选择不保存。
这样这个类就很灵活了,使用范围也可以广泛一点。
在实现这个函数的时候,遇到了两大难题,一个是如何操作隐藏域,另一个是如何“自动”保存和“自动”加载。 ViewState可是不用单独调用 SaveViewState()来保存数据的。
在 Class里面操作 cookie、 Session等还是比较容易的( System.Web.HttpContext.Current.Response.Cookies[ClientID]),可是如何控制隐藏域呢?想了好久也没有想到好的方法,只好用笨方法了——传递一个 Page实例( System.Web.UI.Page)进来,然后使用 Page.ClientScript.RegisterHiddenField(ClientID, myData) 来搞定。
至于自动保存,也是采用了一个笨笨的方法,既然已经把 Page传递进来了,那么就给他加一个事件吧, _page.PreRender += new EventHandler(MyPage_PreRender);在执行 Render之前保存数据。我想用类似的思路来搞定自动加载数据的( _page.PreLoad += new EventHandler(MyPage_PreLoad);),但是遇到了一个小问题。我们一般都是习惯在 Page_Load函数里面给属性赋值,但是我要加的事件却是在 Page_Load之前执行,也就是说如果在 Page_Load里面赋值的话,即使把事件加上了,那么也早已经失去了执行的机会。当然可以在 OnInit里面给属性赋值,只是这么做不太符合习惯。
我也研究了一下 IStateManager 这个接口,也试了一下,可惜没有成功,也许是我功力不够的原因吧。
Ps:这个难题解决之后, QuickPager分页控件就可以一份为二了,变成 QuickPager_UI、 QuickPager_SQL两个部分,再加上我的数据访问函数库和现实数据的控件,就是一套完整的分页解决方案了。
QuickPager_SQL就是专门处理分页算法(也就是分页用的 SQL语句)的,这些部分都可以独立使用,也可以替换成其他的控件、类库。
下面是源码,源文件等整理之后和分页控件一起发送。
namespace JYK.Common { 枚举enum SaveViewStateLocation #region 枚举enum SaveViewStateLocation /**/ /// <summary> /// 保存数据的位置 /// </summary> public enum SaveViewStateLocation { /**/ /// <summary> /// 不保存 /// </summary> NoSave = 1 , /**/ /// <summary> /// 放在Cookie里面保存 /// </summary> Cookie = 2 , /**/ /// <summary> /// 放在隐藏域里面保存 /// </summary> Hidden = 3 , /**/ /// <summary> /// 放在Session里面保存 /// </summary> Session = 4 , /**/ /// <summary> /// 放在Cache里面保存 /// </summary> Cache = 5 , /**/ /// <summary> /// 放在Application里面保存 /// </summary> Application = 6 } #endregion public class MyViewState // : IStateManager { 成员 #region 成员 /**/ /// <summary> /// 保存数据的字典 /// </summary> private Dictionary < string , string > vs = new Dictionary < string , string > (); /**/ /// <summary> /// 用于给表单里面添加隐藏域和加事件 /// </summary> private System.Web.UI.Page _page; /**/ /// <summary> /// 密钥 /// </summary> private string _key = "" ; #endregion public MyViewState() { // 默认设置为不保存 SaveLocation = SaveViewStateLocation.NoSave; } 属性 #region 属性 /**/ /// <summary> /// 存放数据的位置 /// </summary> public SaveViewStateLocation SaveLocation; /**/ /// <summary> /// 密钥,不同的密钥会生成不同的密文。空字符串表示不需要加密 /// </summary> public string Key { set { _key = value; } get { return _key; } } /**/ /// <summary> /// 保存数据的标识 /// </summary> public string ClientID = " myVS " ; /**/ /// <summary> /// 传递Page实例,以实现自动保存数据,和添加隐藏域的功能 /// </summary> public Page Page { set { _page = value; _page.PreLoad += new EventHandler(MyPage_PreLoad); // 本来想在Page_Load之前加载内容,但是出现了一点问题 _page.PreRender += new EventHandler(MyPage_PreRender); // 自动保存内容 } get { return _page; } } 索引器,类似于ViewState的使用方式 #region 索引器,类似于ViewState的使用方式 /**/ /// <summary> /// 索引器,类似于ViewState的使用方式 /// </summary> /// <param name="key"></param> /// <returns></returns> public string this [ string key] { set { if (vs.ContainsKey(key)) { vs[key] = value; } else { vs.Add(key, value); } } get { if (vs.ContainsKey(key)) { return vs[key]; } else { return null ; } } } #endregion #endregion 用于自动加载和保存数据的事件 #region 用于自动加载和保存数据的事件 void MyPage_PreRender( object sender, EventArgs e) { SaveViewState(); // throw new NotImplementedException(); } void MyPage_PreLoad( object sender, EventArgs e) { LoadViewState(); // throw new NotImplementedException(); } #endregion 函数 #region 函数 /**/ /// <summary> /// 把数据保存到指定的位置里面 /// </summary> public void SaveViewState() { // 拼接字符串 System.Text.StringBuilder str = new StringBuilder( 1000 ); foreach (KeyValuePair < string , string > entry in vs) { str.Append(entry.Key); str.Append( " ` " ); str.Append(entry.Value); str.Append( " ` " ); } if (str.Length == 0 ) // 没有赋值 return ; str.Remove(str.Length - 1 , 1 ); string myData = str.ToString(); if (Key.Length > 0 ) { // 加密 myData = Encryptor.Encrypt(myData, Key); } str.Length = 0 ; 保存 #region 保存 switch (SaveLocation) { case SaveViewStateLocation.Cookie : System.Web.HttpContext.Current.Response.Cookies[ClientID].Value = myData; break ; case SaveViewStateLocation.Hidden: #region if (Page != null ) { Page.ClientScript.RegisterHiddenField(ClientID, myData); } #endregion break ; case SaveViewStateLocation.Session : System.Web.HttpContext.Current.Session[ClientID] = myData; break ; case SaveViewStateLocation.Cache : System.Web.HttpContext.Current.Cache[ClientID] = myData; break ; case SaveViewStateLocation.Application : System.Web.HttpContext.Current.Application[ClientID] = myData; break ; } #endregion } public void LoadViewState() { // 加载 string str = "" ; string [] arr = null ; switch (SaveLocation) { case SaveViewStateLocation.Cookie: if (System.Web.HttpContext.Current.Request.Cookies[ClientID] == null ) return ; str = System.Web.HttpContext.Current.Request.Cookies[ClientID].Value; break ; case SaveViewStateLocation.Hidden : if (Page == null ) return ; if (System.Web.HttpContext.Current.Request[ClientID] == null ) return ; str = System.Web.HttpContext.Current.Request[ClientID]; break ; case SaveViewStateLocation.Session: str = System.Web.HttpContext.Current.Session[ClientID].ToString(); break ; case SaveViewStateLocation.Cache: str = System.Web.HttpContext.Current.Cache[ClientID].ToString(); break ; case SaveViewStateLocation.Application: str = System.Web.HttpContext.Current.Application[ClientID].ToString(); break ; } if (str.Length == 0 ) // 没有取到值 return ; if (Key.Length > 0 ) { // 加密 str = Encryptor.Decrypt(str, Key); } // 拆分 arr = str.Split( ' ` ' ); // 赋值 for ( int i = 0 ; i < arr.Length; i += 2 ) { vs.Add(arr[i], arr[i + 1 ]); } } #endregion } }