加载字幕保存,WebForm —— 页面状态自动加载和保存(下)

很久之前写完了上、中两篇,因为各种原因吧,到现在也没有完成下篇,心里一直有些愧疚。好了,废话不说了,把下篇补上,也是我用到现在的代码。
第一步,新建一个类,并且让类从 BasePage 继承。
第二步,重写 BasePage 类的两个虚方法:GetCacheData 和 SaveCacheData ,分别处理数据的 Load 和 Save 。
第三步,保存这个类,并让页面的后台类(系统默认继承自 Page)继承自这个新类就可以了。
好了,步骤理解之后,原理在上中两篇说的差不多了,剩下的看代码就行了,有问题给我留言就 OK 。
首先是中篇中提到的的 AutoSaveAttribute 特性类:
using System; using System.Diagnostics; namespace Lenic.Web { /// /// 自动保存属性,配合 BasePage 能够实现 Web 页面后台代码类字段或属性的自动保存和加载。 /// [DebuggerStepThrough] [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public class AutoSaveAttribute : Attribute { /// /// 初始化创建一个 类的实例,使得具有该属性的类的属性或字段具有自动保存的特性。 /// public AutoSaveAttribute() { } } }
然后是核心处理逻辑 BasePage 虚基类:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Web.UI; namespace Lenic.Web { /// /// 提供了 Web 页面自动保存属性处理的基类 /// [DebuggerStepThrough] public abstract class BasePage : Page { #region Reload Fields And Properties /// /// 引发 事件。 /// /// 包含事件数据的 对象。
protected override void OnLoad(EventArgs e) { // 初始化当前用户控件的缓冲字典 InitCacheDic(); if (Page.IsPostBack) { // 获得缓冲数据列表 var list = GetCacheData(); // 自动加载 AutoSave 属性保存的值 int index = 0; foreach (MemberInfo info in CacheDic[CurrType]) { if (info.MemberType == MemberTypes.Property) { PropertyInfo pi = info as PropertyInfo; object value = list[index]; if (value != null) pi.SetValue(this, value, null); } else if (info.MemberType == MemberTypes.Field) { FieldInfo fi = info as FieldInfo; object value = list[index]; fi.SetValue(this, value); } index++; } } base.OnLoad(e); } #endregion #region Save Fields And Properties /// /// 在这里实现属性的自动保存。 /// protected override object SaveViewState() { // 初始化当前用户控件的缓冲字典 InitCacheDic(); // 初始化要保存的属性值列表 List list = new List(); foreach (MemberInfo info in CacheDic[CurrType]) { if (info.MemberType == MemberTypes.Property) { PropertyInfo pi = info as PropertyInfo; list.Add(pi.GetValue(this, null)); } else if (info.MemberType == MemberTypes.Field) { FieldInfo fi = info as FieldInfo; list.Add(fi.GetValue(this)); } } // 保存更改 SaveCacheData(list); return base.SaveViewState(); } #endregion #region Business Properties /// /// 用户控件类型及自动保存属性成员缓冲字典 /// protected static Dictionary CacheDic = null; /// /// 当前页面的类型 /// protected Type CurrType = null; /// /// 获得成员列表的绑定标识. /// private static readonly BindingFlags Flag; /// /// 初始化 类. /// static BasePage() { CacheDic = new Dictionary(); Flag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.FlattenHierarchy; } /// /// 初始化当前页面的缓冲字典 /// private void InitCacheDic() { // 获得当前实例类型 CurrType = GetType(); MemberInfo[] mems = null; if (!CacheDic.TryGetValue(CurrType, out mems)) { var list = CurrType.GetMembers(Flag) .Where(p => Attribute.IsDefined(p, typeof(AutoSaveAttribute), false)) .ToArray(); CacheDic[CurrType] = list; } } #endregion #region Data Fetch /// /// 获得缓存的数据。 /// /// 重获的数据。 protected abstract List GetCacheData(); /// /// 保存需要缓存的数据。 /// /// 需要保存的数据数组。
protected abstract void SaveCacheData(List data); #endregion } }
其次是对 GetCacheData 方法和 SaveCacheData 的实现:
private ICacheList _currCache = null; private ICacheList CurrCache { get { if (_currCache == null) { _currCache = SqliteCacheList.NewInstance(CurrUser.SessionID, CurrUser.ID, CurrType.FullName); _currCache.LoadPageData(); } return _currCache; } } protected override List GetCacheData() { var data = CurrCache.Where(p => p.PageID == CurrType.FullName) .Select(p => p.Data == null ? null : p.Data.DeserializeFromByte()) .ToList(); return data; } protected override void SaveCacheData(List data) { data.ForEach((p, i) => CurrCache[i].Data = p == null ? null : p.SerializeToByte()); CurrCache.Save(); }
这里用到了一些自定义的扩展方法:
List 类的 ForEach 方法添加了第二个参数 i 表示当前索引值。
DeserializeFromByte 是 byte[] 字节数组反序列化为 T 类型对象的扩展。
SerializeToByte 是将当前对象序列化为 byte[] 字节数组的扩展。
这三个方法应该问题都不大,百度一下就能找到类似的实现,这里就不再贴代码了。
再次是缓存的具体实现代码。下面的代码是我个人实现的方法,每个人的想法可能都不同,权当抛砖引玉了:
观察仔细的童鞋,可以看到 ICacheList 接口,这就是我自定义的一个接口:
using System.Collections.Generic; namespace Lenic.Web.Caches { /// /// 缓存列表 /// public interface ICacheList : IEnumerable { /// /// 【自动新建】获得或设置缓存项。 /// /// 项的索引
/// 缓存项 CacheItem this[int i] { get; set; } /// /// 从数据库中加载数据 /// /// 数据的类型 /// 数据标识
/// 还原的原始数据 T LoadData(string dataId); /// /// 从数据库中加载页面数据 /// /// 加载后的列表 ICacheList LoadPageData(); /// /// 持久化数据变化 /// void Save(); } }
其中 IEnumerable 中的 CacheItem 表示缓存项的虚基类:
using System; using System.Diagnostics; using Lenic.Data; using Lenic.Extensions; namespace Lenic.Web.Caches { /// /// 缓存项 /// [Serializable] [DebuggerStepThrough] public abstract class CacheItem { #region Instance /// /// 初始化创建一个 类的对象。 /// public CacheItem() { MarkNew(); } /// /// 初始化创建一个 类的对象。 /// /// true 表示是从数据库中填充获得的; 否则返回 false
public CacheItem(bool isFetched) { if (isFetched) MarkFetched(); else MarkNew(); } /// /// 初始化创建一个 类的对象。 /// /// 会话标识
/// 用户标识
/// 页面标识
/// 数据标识
/// 获得或新建的一个 类的对象 public CacheItem(string sessionID, string userID, string pageID, string dataID) : this() { SessionID = sessionID; UserID = userID; PageID = pageID; DataID = dataID; } #endregion #region Business Properties /// /// 获得或设置当前会话标识。 /// public string SessionID { get; set; } /// /// 获得当前用户标识。 /// public string UserID { get; set; } /// /// 获得或设置当前页面标识。 /// public string PageID { get; set; } /// /// 获得或设置当前数据标识。 /// public string DataID { get; set; } private byte[] _data = null; /// /// 获得或设置当前数据对象数据。 /// public byte[] Data { get { return _data; } set { _data = value; IsDirty = true; } } /// /// 获得最近一次的修改时间 /// public DateTime LastChanged { get; set; } /// /// 获得当前数据对象。 /// public object DataObject { get { return Data == null ? null : Data.DeserializeFromByte(); } } #endregion #region Mark Instance /// /// 获得当前实例是否是新建、未保存到数据库中。 /// public bool IsNew { get; protected set; } /// /// 获得当前实例是否是否是从数据库中检索并填充的。 /// public bool IsFetched { get; protected set; } /// /// 获得一个值, 通过该值指示当前实例对象是否被修改过。 /// /// true 表示被修改过; 否则返回 false public bool IsDirty { get; protected set; } /// /// 获得当前实例是否已经标识为删除。 /// public bool IsDeleted { get; protected set; } /// /// 标识当前对象是新建、未保存到数据库中。 /// /// 更改后的自身。 public CacheItem MarkNew() { IsDirty = false; IsNew = true; IsFetched = false; IsDeleted = false; return this; } /// /// 标识当前对象为从数据库中检索并填充的。 /// /// 更新后的自身。 public CacheItem MarkFetched() { IsDirty = false; IsNew = false; IsFetched = true; IsDeleted = false; return this; } /// /// 标识当前实例对象需要在保存时删除。 /// /// 修改后的自身。 public CacheItem MarkDeleted() { IsDirty = true; IsDeleted = true; return this; } #endregion #region Equal public override bool Equals(object obj) { if (obj == null || typeof(CacheItem) != obj.GetType()) return false; var target = obj as CacheItem; if (this.SessionID == target.SessionID && this.UserID == target.UserID && this.PageID == target.PageID && this.DataID == target.DataID) return true; return base.Equals(obj); } public override int GetHashCode() { int hash = SessionID.GetHashCode(); hash ^= UserID.GetHashCode(); hash ^= PageID.GetHashCode(); hash ^= DataID.GetHashCode(); return hash; } #endregion #region Data Operater /// /// 持久化到数据库中。 /// /// 数据库操作实例对象
/// 影响的行数。 public abstract int Save(IDataAccesser t); #endregion } }
在项目中我用 Sqlite 写了一个实现,具体代码如下:
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using Lenic.Data; using Lenic.Data.Extensions; using System.Linq; using Lenic.Extensions; namespace Lenic.Web.Caches.Sqlite { /// /// 页面 ViewState 数据库缓冲 /// [Serializable] [DebuggerStepThrough] public class SqliteCacheList : ICacheList { private List list = new List(); #region DbHelper /// /// 缓存数据库连接字符串 /// public static string ConnectionString = @"Data Source=|DataDirectory|\PageCache.dll"; /// /// 数据库访问实例对象 /// private static IDataAccesser DB = new DbHelper(DbProviders.SQLiteProvider, ConnectionString); /// /// 查询缓冲表是否在数据库中存在, 返回一个 类型的值表示找到的个数. /// private const string SelectExists = "SELECT COUNT(*) AS COUNTS FROM SQLITE_MASTER WHERE TYPE = 'table' AND NAME = 'TB_Page'"; /// /// 创建缓冲表语句. /// private const string CreateTable = @"CREATE TABLE [TB_Page] ( [SessionID] nvarchar(50) NOT NULL, [UserID] nvarchar(50) NOT NULL, [PageID] nvarchar(50) NOT NULL, [DataID] nvarchar(50) NOT NULL, [Data] blob, [LastChanged] timestamp NOT NULL )"; /// /// 删除缓冲表语句. /// private const string DropTable = "Drop Table TB_Page"; /// /// 收缩数据库语句. /// private const string ShrinkDB = "Vacuum"; /// /// 清除用户之前的记录文本. /// private const string ClearPreviousText = "DELETE FROM [TB_Page] WHERE [UserID] = '{0}'"; /// /// 清除指定 SessionID 指定用户的的记录文本. /// private const string ClearSessionText = "DELETE FROM [TB_Page] WHERE [SessionID] = {0} AND [UserID] = '{1}'"; /// /// 【页面缓存】0 = 会话标识 AND 1 = 用户标识 AND 2 = 页面标识 /// private const string SelectPageSql = "SELECT * FROM [TB_Page] WHERE [SessionID] = '{0}' AND [UserID] = '{1}' AND [PageID] = '{2}'"; /// /// 【变量数据】0 = 会话标识 AND 1 = 用户标识 AND 2 = 页面标识 AND 3 = 数据标识 /// private const string SelectSingleDataSql = "SELECT [Data] FROM [TB_Page] WHERE [SessionID] = '{0}' AND [UserID] = '{1}' AND [PageID] = '{2}' AND [DataID] = '{3}'"; #endregion #region Business Properties /// /// 获得会话标识 /// public string SessionID { get; private set; } /// /// 获得用户标识 /// public string UserID { get; private set; } /// /// 获得页面标识 /// public string PageID { get; private set; } #endregion #region New Instance private SqliteCacheList() { } /// /// 新建一个列表对象 /// public static SqliteCacheList NewInstance(string sessionID, string userID, string pageID) { return new SqliteCacheList { SessionID = sessionID, UserID = userID, PageID = pageID, }; } /// /// 【自动新建】获得或设置缓存项。 /// /// /// 缓存项 public CacheItem this[int i] { get { var item = list.ElementAtOrDefault(i); if (item == null) { item = new SqliteCacheItem(SessionID, UserID, PageID, i.ToString()); list.Add(item); } return item; } set { if (i >= list.Count) list.Add((SqliteCacheItem)value); else { list.RemoveAt(i); list.Insert(i, (SqliteCacheItem)value); } } } #endregion #region IEnumerable 成员 /// /// 返回一个循环访问集合的枚举数。 /// /// 可用于循环访问集合的 System.Collections.Generic.IEnumeratorlt;SqliteCacheItemgt;。 public IEnumerator GetEnumerator() { var data = list.GetEnumerator(); while (data.MoveNext()) { yield return (CacheItem)data.Current; } } /// /// 返回一个循环访问集合的枚举数。 /// /// 可用于循环访问集合的 System.Collections.Generic.IEnumeratorlt;SqliteCacheItemgt;。 IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// /// 返回一个循环访问集合的枚举数。 /// /// 可用于循环访问集合的 System.Collections.IEnumerator。 IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion #region Data Operater /// /// 初始化缓冲数据库。 /// /// 如果设置为 true 标识先删除 Table 再重建。
public static void Init(bool deleteTable) { long count = 0; using (DataReaderEx con = DB.Read(SelectExists)) { count = con.Field("COUNTS"); } if (count > 0 && deleteTable) DB.Write(DropTable); if (count == 0) DB.Write(CreateTable); DB.Write(ShrinkDB); } /// /// 从数据库中清除用户旧的记录, 立即生效! /// /// 用户标识.
public static void ClearPrevious(string userId) { DB.Write(ClearPreviousText.With(userId)); } /// /// 持久化数据变化 /// public void Save() { using (var con = DbProviders.SQLiteProvider.CreateConnection()) { con.ConnectionString = ConnectionString; con.Open(); var transaction = con.BeginTransaction(); var t = new TransactionHelper(transaction); try { foreach (var item in this) { int count = item.Save(t); if (count == 0) throw new DatabaseException("数据库操作失败"); } t.Commit(); } catch (Exception e) { t.Rollback(); throw e; } } } /// /// 从数据库中加载页面数据 /// /// 会话标识
/// 用户标识
/// 页面标识
/// 加载后的列表 public ICacheList LoadPageData() { if (SessionID.IsNullOrEmptyTrim()) throw new ApplicationException("会话标识不能为空"); if (UserID.IsNullOrEmptyTrim()) throw new ApplicationException("用户标识不能为空"); if (PageID.IsNullOrEmptyTrim()) throw new ApplicationException("页面标识不能为空"); using (var con = DbProviders.SQLiteProvider.CreateConnection()) { con.ConnectionString = ConnectionString; con.Open(); var transaction = con.BeginTransaction(); var t = new TransactionHelper(transaction); try { using (DataReaderEx dr = t.Read(SelectPageSql.With(SessionID, UserID, PageID))) { list = dr.ToList(p => new SqliteCacheItem(true) { SessionID = p.Field("SessionID"), UserID = p.Field("UserID"), PageID = p.Field("PageID"), DataID = p.Field("DataID"), Data = p.Field("Data"), LastChanged = p.Field("LastChanged"), }); } } finally { t.Rollback(); } } return this; } /// /// 从数据库中加载数据 /// /// 数据的类型 /// 会话标识
/// 用户标识
/// 数据标识
/// 还原的原始数据 public T LoadData(string dataId) { if (SessionID.IsNullOrEmptyTrim()) throw new ApplicationException("会话标识不能为空"); if (UserID.IsNullOrEmptyTrim()) throw new ApplicationException("用户标识不能为空"); var data = DB.GetValue(SelectSingleDataSql.With(SessionID, UserID, "Lenic.Global", dataId)) .DirectTo(); if (data == null) return default(T); return data.DeserializeFromByte(); } #endregion } }
下面是缓存项的实现类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using Lenic.Data; using Lenic.Extensions; namespace Lenic.Web.Caches.Sqlite { /// /// Sqlite 缓存项 /// [Serializable] [DebuggerStepThrough] public class SqliteCacheItem : CacheItem { #region Instance /// /// 初始化创建一个 类的对象。 /// public SqliteCacheItem() { MarkNew(); } /// /// 初始化创建一个 类的对象。 /// /// true 表示是从数据库中填充获得的; 否则返回 false
public SqliteCacheItem(bool isFetched) { if (isFetched) MarkFetched(); else MarkNew(); } /// /// 初始化创建一个 类的对象。 /// /// 会话标识
/// 用户标识
/// 页面标识
/// 数据标识
/// 获得或新建的一个 类的对象 public SqliteCacheItem(string sessionID, string userID, string pageID, string dataID) : this() { SessionID = sessionID; UserID = userID; PageID = pageID; DataID = dataID; } #endregion #region Command Text /// /// 【插入】0 = 会话标识 AND 1 = 用户标识 AND 2 = 页面标识 AND 3 = 数据标识 AND 4 = 插入序号 /// internal const string InsertText = "INSERT INTO [TB_Page]([SessionID], [UserID], [PageID], [DataID], [Data], [LastChanged]) Values('{0}', '{1}', '{2}', '{3}', @p{4}, datetime());"; /// /// 【更新】0 = 会话标识 AND 1 = 用户标识 AND 2 = 页面标识 AND 3 = 数据标识 AND 4 = 更新序号 /// internal const string UpdateText = "UPDATE [TB_Page] SET [Data] = @p{4}, [LastChanged] = datetime() WHERE [SessionID] = '{0}' AND [UserID] = '{1}' AND [PageID] = '{2}' AND [DataID] = '{3}'"; /// /// 【删除】0 = 会话标识 AND 1 = 用户标识 AND 2 = 页面标识 AND 3 = 数据标识 /// internal const string DeleteText = "Delete From [TB_Page] WHERE [SessionID] = '{0}' AND [UserID] = '{1}' AND [PageID] = '{2}' AND [DataID] = '{3}'"; #endregion #region Data Operater /// /// 持久化到数据库中。 /// /// 数据库操作实例对象
/// 影响的行数。 public override int Save(IDataAccesser t) { if (IsNew && IsDeleted) return -1; if (!IsDirty) return -1; if (IsDeleted) return t.WriteD(DeleteText.With(SessionID, UserID, PageID, DataID)); if (IsNew) return t.WriteD(InsertText.With(SessionID, UserID, PageID, DataID, 0), Data); if (IsFetched) return t.WriteD(UpdateText.With(SessionID, UserID, PageID, DataID, 0), Data); return 0; } #endregion } }
就到这里吧,能拿出来的都拿出来了。后面的代码也包含了一些自定义方法,我略作解释:
IDataAccesser 接口操作数据库,包含下面的方法,具体靠 DbHelper 和 TransactionHelper 实现,我就不写了,你应该能写出来一个实现类。很简单的!
/// /// 数据库操作接口 /// public interface IDataAccesser { /// /// 创建一个新的数据库命令对象。 /// /// 一个新的数据库命令对象。 DbCommand NewCommand(); /// /// 执行查询, 并返回查询所返回的结果集中第一行的第一列. 所有其他的列和行将被忽略. /// /// 查询命令实例对象.
/// 结果集中第一行的第一列. object GetValue(DbCommand cmd); /// /// 从数据库中查询并返回结果集(DataSet 类型)。 /// /// 查询命令实例对象。
/// 查询结果集。 DataSet Query(DbCommand cmd); /// /// 从数据库中查询并返回一个 类型的实例对象。 /// /// 查询命令实例对象。
/// 查询结果集。 DbDataReader Read(DbCommand cmd); /// /// 执行数据库操作, 并返回影响的行数。 /// /// 执行命令实例对象。
/// 影响的行数。 int Write(DbCommand cmd); }
DataReaderEx 是 DbDataReader 的一个实现类,用修饰模式实现,这里你可以把其当成 IDataReader 接口来看待。
IsNullOrEmptyTrim 是对 String 类 IsNullOrEmpty 方法的封装,同时增加了对 Trim 方法处理后的判断。
DirectTo 是对类型强转的包装,等于 (T)obj 。
With 是对 String.Format 的封装。
Tags:  页面加载失败 uc页面加载失败 页面加载顺序 webform 加载字幕保存

延伸阅读

最新评论

发表评论