ASP.NET Session 辅助器(作用域、分类)






4.35/5 (11投票s)
使用作用域和分类来避免 Session 值之间的冲突和混淆。
引言(关于 ASP.NET Session 的更多信息)
如果您正在开发 ASP.NET Web 应用程序,那么您肯定遇到过需要存储和检索每个用户在浏览您的 ASP.NET Web 应用程序页面时属于该用户的某些持久化值的情况,因此您自然而然地使用了 Session。
有关使用 Session 的更多信息,请访问:MSDN
Session 本质上就是一个 Dictionary
(一个基于键值对的集合),其键类型为 string
,值类型为 object
(即任何继承自 object
的可序列化对象)。
您可以通过当前 HTTP 上下文 System.Web.HttpContext.Current.Session
或页面的快捷引用 System.Web.UI.Page.Session
从“任何地方”访问 Session。
具体来说,当您想在 Session 中存储一个值时,您可能会编写这样的代码行(而不关心任何 Session 管理器)
// Store value to session
int myValue = 10;
Session["MyKey"] = myValue;
然后,您可能会编写以下代码来检索先前存储的值,主要(但不仅限于)从另一个页面(或控件)
// Retrieve value from session (up cast needed)
int myValue = (int)Session["MyKey"];
请注意,如果您编写了自己的类并希望将其存储到 Session 中,则需要添加 [Serializable]
属性。
Session 是原始的,这里是 SessionHelper
正如您所见,Session 有点原始,当您想使用高级功能(如分类值、属于特定页面或控件的值等)时,您很快就会感到厌烦。
在这些情况下,您应该(必须)使用 Session,但您迟早会遇到一些冲突问题!
解决此问题的一种方法(请告知您的反馈)是使用“作用域和分类的” Session 值。这不是 ASP.NET 框架的一项功能,而是我构建并成功使用的 Session 辅助器,因此我很高兴与您分享这个技巧。
什么是“作用域和分类的” Session 值?无非就是根据多个参数生成其键的 Session 值:它的作用域(例如:“Global”),它的可选分类(例如:“Filter”),以及它的句柄/键(例如:“ItemsPerPage
”)。
因此,我们将使用格式化的键值对而不是原始的键值对(请注意,每个令牌都用点分隔)
Session["Scope.Category.Key"]
- 可用的作用域如下(您可以自行发明和实现):
Global
(与原始 Session 相同的作用域)Page
(当前页面,不包含查询字符串参数)PageAndQuery
(当前页面,包含查询字符串参数)- 可用的作用域分类仅限于您的想象,例如“Visitor”或“Filter”。
- 键是最终值的名称/句柄;选择一个简短而清晰的,例如:“Surname”。
因此,以下是使用此方案存储 **全局** 值的方法
/*
* In "OnePage.aspx"
*/
// Store value to session
Session["Global.Visitor.IPAddress"] =
HttpContext.Current.User.Identity.Name;
/*
* In "AnotherPage.aspx"
*/
// Retrieve value from session (up cast needed)
string visitorIPAddress =
(string)Session["Global.Visitor.IPAddress"];
以下是限制存储值到 **特定页面** 的代码(其中“PageHash
”是页面 URL 的 MD5 或 SHA-1 值):
/*
* In "OnePage.aspx"
*/
// Store page's filter value to session
Session["PageHash.Filter.Surname"] = textBoxSurname.Text;
/*
* In "AnotherPage.aspx"
*/
// Retrieve value from session (downcast needed), WILL NOT WORKS!!
string onePageSurnameFilter = (string)Session["PageHash.Filter.Surname "];
/*
* In "OnePage.asp"
*/
// Retrieve value from session (downcast needed), WORKS!!
textBoxSurname.Text = (string)Session["PageHash.Filter.Surname"];
原理是好的,但方法仍然原始且现在难以使用,因此,是时候实现 Session 辅助类了。
实现辅助器
为了实现“作用域和分类的”功能,我选择构建一个名为 SessionHelper
的辅助类(请不要因为缺乏创意而责怪我;)
您可以直接在页面中使用此类,但我建议您创建继承自 ASP.NET 框架类(System.Web.UI.*
)的基类,并将此辅助类包装在其中。如果您还没有在 Web 应用程序中这样做,这将为您节省一些枯燥的代码(这将在“包装辅助器”部分进行解释)。
让我们用类体开始代码
public class SessionHelper
{
...
}
像往常一样,让我们从枚举开始。在这里,我使用一个枚举来表示可用的作用域(全局、到页面但不包含查询字符串、到页面并包含查询字符串)
...
#region Enumerators
/// <summary>
/// Available session scopes
/// </summary>
public enum Scope
{
Global,
Page,
PageAndQuery
/* put any other scopes you may find useful here */
}
#endregion
...
没有实例成员或访问器,因此我们可以直接开始编写静态方法。为了使类更加完善,我们必须允许开发人员存储、检索、搜索和清除值,但内部,我们必须先编写一些私有实用函数。首先要做的是生成格式化后的键。这里是通过一些重载方法完成的
#region Session's key format
/// <summary>
/// Format a key, using a scope a category and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="category"></param>
/// <param name="key"></param>
/// <returns></returns>
private static string FormatKey(Scope scope, string category, string key)
{
// clean up any points
string scopeHash = GetScopeHash(scope);
category = category.Replace(".", "");
key = key.Replace(".", "");
return string.Format("{0}.{1}.{2}", scopeHash, category, key);
}
/// <summary>
/// Format a key, using a category and a key (global scope)
/// </summary>
/// <param name="scope"></param>
/// <param name="key"></param>
/// <returns></returns>
private static string FormatKey(string category, string key)
{
return FormatKey(Scope.Global, category);
}
/// <summary>
/// Format a key, using a scope and a key
/// </summary>
/// <param name="category"></param>
/// <param name="key"></param>
/// <returns></returns>
private static string FormatKey(Scope scope, string key)
{
return FormatKey(scope, string.Empty);
}
/// <summary>
/// Format a key, using a key (global scope)
/// </summary>
/// <param name="scope"></param>
/// <param name="key"></param>
/// <returns></returns>
private static string FormatKey(string key)
{
return FormatKey(string.Empty);
}
#endregion
您可能已经注意到调用了 GetScopeHash
;此方法提供与给定作用域对应的哈希值,以避免任何冲突。因此,“硬核开发者”将不得不在这里处理他们的自定义作用域(如果需要)(代码如下)。请注意,我调用了基于 MD5 哈希的 GetHash
方法(来源:MSDN)。
另一方面,SessionKey
根据给定的作用域、分类和句柄,提供对 HttpContext.Current.Session
的快捷访问。
StoreFormatedKey
和 ClearFormatedKey
是低级方法,分别用于将值存储到格式化键以及从中检索值。
最后,ClearStartsWith
是一个辅助方法,用于清除所有以给定字符串开头的格式化键。
#region Cryptography
/// <summary>
/// Creates a MD5 based hash
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private static string GetHash(string input)
{
// step 1, calculate MD5 hash from input
System.Security.Cryptography.MD5 md5 =
System.Security.Cryptography.MD5.Create();
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] hash = md5.ComputeHash(inputBytes);
// step 2, convert byte array to hex string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
#endregion
/// <summary>
/// Get the hash of a scope
/// </summary>
/// <param name="scope"></param>
/// <returns></returns>
private static string GetScopeHash(Scope scope)
{
// Get scope name
string scopeName = Enum.GetName(scope.GetType(), scope);
switch (scope)
{
case Scope.Page:
scopeName = HttpContext.Current.Request.Url.AbsoluteUri;
if (HttpContext.Current.Request.Url.Query != string.Empty)
{
scopeName = scopeName.Replace(
HttpContext.Current.Request.Url.Query, "");
}
break;
case Scope.PageAndQuery:
scopeName = HttpUtility.UrlDecode(
HttpContext.Current.Request.Url.AbsoluteUri);
break;
}
return GetHash(scopeName);
}
/// <summary>
/// Shortcut to formated session value
/// </summary>
/// <param name="scope"></param>
/// <param name="category"></param>
/// <param name="key"></param>
/// <returns></returns>
private static object SessionKey(Scope scope, string category, string key)
{
return HttpContext.Current.Session[FormatKey(scope, category, key)];
}
/// <summary>
/// Rawly store a value in a formated key
/// </summary>
/// <param name="formatedKey"></param>
/// <param name="value"></param>
private static void StoreFormatedKey(string formatedKey, object value)
{
HttpContext.Current.Session[formatedKey] = value;
}
/// <summary>
/// Rawly clear a formated key value
/// </summary>
/// <param name="formatedKey"></param>
private static void ClearFormatedKey(string formatedKey)
{
HttpContext.Current.Session.Remove(formatedKey);
}
/// <summary>
/// Clears all formated keys starting with given value
/// </summary>
/// <param name="startOfFormatedKey"></param>
private static int ClearStartsWith(string startOfFormatedKey)
{
List<string> formatedKeysToClear = new List<string>();
// Gather formated keys to clear
// (to prevent collection modification during parsing)
foreach (string key in HttpContext.Current.Session)
{
if (key.StartsWith(startOfFormatedKey))
{
// Add key
formatedKeysToClear.Add(key);
}
}
foreach (string formatedKey in formatedKeysToClear)
{
ClearFormatedKey(formatedKey);
}
return formatedKeysToClear.Count;
}
#endregion
现在是时候检查一个键是否存在了(基本通过检查 Session 值是否为 null
来完成),在这些重载方法的帮助下
#region Key existence
/// <summary>
/// Indicates if the key associated to given scope and category exists
/// </summary>
/// <param name="scope"></param>
/// <param name="category"></param>
/// <param name="key"></param>
public static bool Exists(Scope scope, string category, string key)
{
return SessionKey(scope, category, key) != null;
}
/// <summary>
/// Indicates if the key associated to given category exists (global scope)
/// </summary>
/// <param name="category"></param>
/// <param name="key"></param>
public static bool Exists(string category, string key)
{
return Exists(Scope.Global, category, key);
}
/// <summary>
/// Indicates if the key associated to given scope exists
/// </summary>
/// <param name="scope"></param>
/// <param name="key"></param>
public static bool Exists(Scope scope, string key)
{
return Exists(scope, string.Empty);
}
/// <summary>
/// Indicates if the key exists (global scope)
/// </summary>
/// <param name="key"></param>
public static bool Exists(string key)
{
return Exists(string.Empty, key);
}
#endregion
然后,我们提供一些重载方法,用于根据作用域、分类和键存储值(请注意,我们在这里使用了 StoreFormattedKey
)
#region Values storing
/// <summary>
/// Stores a value to session, using a scope a category and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="category"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Store(Scope scope, string category, string key, object value)
{
StoreFormattedKey(FormatKey(scope, category, key), value);
}
/// <summary>
/// Stores a value to session, using a category and a key (global scope)
/// </summary>
/// <param name="category"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Store(string category, string key, object value)
{
Store(Scope.Global, category, key, value);
}
/// <summary>
/// Stores a value to session, using a scope and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="key"></param>
public static void Store(Scope scope, string key, object value)
{
Store(scope, string.Empty, key, value);
}
/// <summary>
/// Stores a value to session, using a key (global scope)
/// </summary>
/// <param name="key"></param>
public static void Store(string key, object value)
{
Store(string.Empty, key, value);
}#endregion
存储一些值后,合乎逻辑的步骤是检索它们。为了避免使用此类来编写枯燥的代码,我们将提供基本的检索(Retrieve
)以及在等待的值不存在时获取默认值的能力(RetrieveWithDefault
)
#region Values retrieving (null if not found)
/// <summary>
/// Stores a value to session, using a scope a category and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="category"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static object Retrieve(Scope scope, string category, string key)
{
return SessionKey(scope, category, key);
}
/// <summary>
/// Stores a value to session, using a category and a key (global scope)
/// </summary>
/// <param name="category"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static object Retrieve(string category, string key)
{
return Retrieve(Scope.Global, category, key);
}
/// <summary>
/// Stores a value to session, using a scope and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="key"></param>
public static object Retrieve(Scope scope, string key)
{
return Retrieve(scope, string.Empty, key);
}
/// <summary>
/// Stores a value to session, using a key (global scope)
/// </summary>
/// <param name="key"></param>
public static object Retrieve(string key)
{
return Retrieve(string.Empty, key);
}
#endregion
#region Values retrieving (with default value)
/// <summary>
/// Stores a value to session, using a scope a category and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="category"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static object RetrieveWithDefault(Scope scope,
string category, string key, object defaultValue)
{
object value = SessionKey(scope, category, key);
return value == null ? defaultValue : value;
}
/// <summary>
/// Stores a value to session, using a category and a key (global scope)
/// </summary>
/// <param name="category"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static object RetrieveWithDefault(string category,
string key, object defaultValue)
{
return RetrieveWithDefault(Scope.Global, category, key, defaultValue);
}
/// <summary>
/// Stores a value to session, using a scope and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="key"></param>
public static object RetrieveWithDefault(Scope scope,
string key, object defaultValue)
{
return RetrieveWithDefault(scope, string.Empty, key, defaultValue);
}
/// <summary>
/// Stores a value to session, using a key (global scope)
/// </summary>
/// <param name="key"></param>
public static object RetrieveWithDefault(string key, object defaultValue)
{
return RetrieveWithDefault(string.Empty, key, defaultValue);
}
#endregion
最后一步是清除已存储值的功能。我们可以清除所有值(Clear
)、属于某个作用域的值(ClearScope
)、某个作用域的某个分类的值(ClearCategory
),或者仅仅是某个键的值(Clear
重载方法)
#region Values clearing
/// <summary>
/// Clears all session values
/// </summary>
public static void Clear()
{
HttpContext.Current.Session.Clear();
}
/// <summary>
/// Clears all session values of given scope
/// </summary>
/// <param name="scope"></param>
/// <returns>Number of affected values</returns>
public static int ClearScope(Scope scope)
{
return ClearStartsWith(string.Format("{0}.", GetScopeHash(scope)));
}
/// <summary>
/// Clears all session values of given scope's category
/// </summary>
/// <param name="scope"></param>
/// <param name="category"></param>
public static int ClearCategory(Scope scope, string category)
{
return ClearStartsWith(string.Format("{0}.{1}.",
GetScopeHash(scope), category));
}
/// <summary>
/// Clears all session values of given category (global scope)
/// </summary>
/// <param name="category"></param>
public static int ClearCategory(string category)
{
return ClearCategory(Scope.Global, category);
}
/// <summary>
/// Clears a session value, using a scope a category and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="category"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Clear(Scope scope, string category, string key)
{
Store(scope, category, key, null);
}
/// <summary>
/// Clears a session value, using a category and a key (global scope)
/// </summary>
/// <param name="category"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Clear(string category, string key)
{
Clear(Scope.Global, category, key);
}
/// <summary>
/// Clears a session value, using a scope and a key
/// </summary>
/// <param name="scope"></param>
/// <param name="key"></param>
public static void Clear(Scope scope, string key)
{
Clear(scope, string.Empty, key);
}
/// <summary>
/// Clears a session value, using a key (global scope)
/// </summary>
/// <param name="key"></param>
public static void Clear(string key)
{
Clear(string.Empty, key);
}
#endregion
大家伙们,就这样了!
在您的项目中使用的 SessionHelper
这个辅助类很容易集成到您的项目中并使用。您可以
- 将类代码移到您的解决方案中
- 使用示例项目中包含的 *Initia.Web* 项目
- 编译并引用 *Initia.Web* 项目的程序集。
完成这些之后,您可以使用 Initia.Web
命名空间,如下所示
using Initia.Web;
然后,您可以开始使用辅助类了。假设您有几个页面,并且您想计算总点击次数、每个页面的点击次数以及点击按钮产生的点击次数。
首先,创建一个页面,然后转到 Page_Load
事件处理程序。我们不必担心 IsPostBack
状态,因为我们希望在每次页面加载时增加点击次数。
protected void Page_Load(object sender, EventArgs e)
{
...
}
以下是将(隐式的)全局作用域“Hits
” Session 值检索出来(默认值为 0)的代码。然后,我们存储增加后的值
...
// Incremented global session value
// (note that it doesn't scope with other "Hits" keys)
int totalHits = (int)SessionHelper.RetrieveWithDefault("Hits", 0);
SessionHelper.Store("Hits", totalHits + 1);
...
非常相似的代码,使用 SessionHelper.Scope.Page
作用域,可以用来在当前页面的作用域内使用(而不必关心查询字符串)。请注意,我们使用的是相同的键,但由于我们使用了不同的作用域,因此没有冲突
...
// Incremented current page session value
// (note that it doesn't scope with other "Hits" keys)
int currentPageHits = (int)SessionHelper.RetrieveWithDefault(
SessionHelper.Scope.Page, "Hits", 0);
SessionHelper.Store(SessionHelper.Scope.Page, "Hits", ++currentPageHits);
...
如果您想将某些 Session 值的作用域限制在当前页面的作用域内(包括查询字符串),您可以使用 SessionHelper.Scope.PageAndQuery
作用域编写这段代码
...
// Incremented current page session value
// (note that it doesn't scope with other "Hits" keys)
int currentPageQueryHits = (int)SessionHelper.RetrieveWithDefault(
SessionHelper.Scope.PageAndQuery, "Hits", 0);
SessionHelper.Store(SessionHelper.Scope.PageAndQuery,
"Hits", ++currentPageQueryHits);
...
最后一个示例是关于分类的 Session 值。这一次,我们将每次用户单击按钮时增加一个点击计数器,并在用户单击另一个按钮时清除该分类。只需在页面中像这样添加按钮
<asp:Button ID="btnAddUserHit" Text="Add hit to user category" runat="server" />
<asp:Button ID="btnClearUserCategory" Text="Clear user category" runat="server" />
然后,在代码隐藏文件中添加事件处理程序(如果您愿意,也可以在 ASPX 页面中完成)
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// Events handlers
btnAddUserHit.Click += new EventHandler(btnAddUserHit_Click);
btnClearUserCategory.Click += new EventHandler(btnClearUserCategory _Click);
}
最后,实现事件处理程序,如下所示(UpdateUI
在下面解释)
#region Events handlers
private void btnAddUserHit_Click(object sender, EventArgs e)
{
// Retrieves, increments then stores
// categorized session value (default value is 0)
int userHits = (int)SessionHelper.RetrieveWithDefault(
"User", "Hits", 0);
SessionHelper.Store("User", "Hits", userHits + 1);
// Update user interface
UpdateUI();
}
private void btnClearGlobalCategory_Click(object sender, EventArgs e)
{
SessionHelper.ClearCategory(SessionHelper.Scope.Global, "User");
// Update user interface
UpdateUI();
}
#endregion
作为“奖励”,这里有一个更新用户界面以反映 Session 值的简单方法(只需在您的 ASPX 页面中添加一个 ID 为 tbUserHits
的文本框)
#region User interface
/// <summary>
/// Update user interface
/// </summary>
public void UpdateUI()
{
// Show categorized session value (default value is 0)
tbUserHits.Text = SessionHelper.RetrieveWithDefault("User",
"Hits", 0).ToString();
}
#endregion
别忘了下载示例项目;它涵盖了类的大部分功能,并将加快类在您的项目中的集成。
兴趣点/结论
希望这个类能帮助您的项目并节省您宝贵的时间。
如果您不喜欢我的方法,并且有更好的方法,请在评论区告知我们。如果这对您有帮助,请留下评论和评分以鼓励我。
感谢
- 感谢 Rafael Rosa 修复了
private static string GetScopeHash(Scope scope)
方法中的编码错误。
历史
- 2008/04/18:初版。
- 2008/04/19:添加了“在您的项目中使用的 SessionHelper”部分。
- 2008/05/10:修复了
private static string GetScopeHash(Scope scope)
方法中的编码错误(感谢 Rafael Rosa 的贡献)。类和示例项目均已更新。