2017-10-18更新
新增:
1)使用者强制制定session的key值,有重复指定危险。慎用,适用于短信验证码、图形验证码等功能。
2)增加微信公众账号支持。
由于公司域名全部要加CDN,原来的运维是把N台机器的Session写到一台机器上。所以用C#原生的Session不会产生任何问题。
由于业务上用session的地方较多,想重写原Session。
把Session存储在Memcached或Redis中。
先了解下ASP.NET——SessionID
知识点:
HTTP 是一种无状态协议。这意味着 Web 服务器会将针对页面的每个 HTTP 请求作为独立的请求进行处理。服务器不会保留以前的请求过程中所使用的变量值的任何信息。
ASP.NET通过会话标识符来判断是否是同一个客户端。简单点说就是依赖ASP.NET的SessionID值来判断,而默认情况下,
SessionID 值存储在 Cookie 中。
Session是依赖于cookie的
如图下图:
会话标识符
会话由一个唯一标识符标识,可使用 SessionID 属性读取此标识符。为 ASP.NET 应用程序启用会话状态时,将检查应用程序中每个页面请求是否有浏览器发送的 SessionID 值。如果未提供任何 SessionID 值,则 ASP.NET 将启动一个新会话,并将该会话的 SessionID 值随响应一起发送到浏览器。
默认情况下,SessionID 值存储在 Cookie 中。但也可以将应用程序配置为在“无 Cookie”会话的 URL 中存储 SessionID 值。
只要一直使用相同的 SessionID 值来发送请求,会话就被视为活动的。如果特定会话的请求间隔超过指定的超时值(以分钟为单位),则该会话被视为已过期。使用过期的 SessionID 值发送的请求将生成一个新的会话。
会话由一个唯一标识符标识,可使用 SessionID 属性读取此标识符。为 ASP.NET 应用程序启用会话状态时,将检查应用程序中每个页面请求是否有浏览器发送的 SessionID 值。如果未提供任何 SessionID 值,则 ASP.NET 将启动一个新会话,并将该会话的 SessionID 值随响应一起发送到浏览器。
默认情况下,SessionID 值存储在 Cookie 中。但也可以将应用程序配置为在“无 Cookie”会话的 URL 中存储 SessionID 值。
只要一直使用相同的 SessionID 值来发送请求,会话就被视为活动的。如果特定会话的请求间隔超过指定的超时值(以分钟为单位),则该会话被视为已过期。使用过期的 SessionID 值发送的请求将生成一个新的会话。
ASP.NET客户端与服务端一般是2种情景
情景一:客服端Cookie中无ASP.NET SessionID。ASP.NET服务端会检测客户端ASP.NET SessionID是否有值,如没有,服务端会回写客户端的ASP.NET SessionID。(注意:ASP.NET的SessionID是只读的,Cookie中默认的ASP.NET_SessionID为永不过期。)
情景二:ASP.NET 客户端每次访问服务端,会带着Cookie中ASP.NET SessionID值,跟服务端交互。当客户端中如有ASP.NET SessionID值,那么服务端的SessionID值就是跟客户端的一致。
如图所示:
分布式Session改造
1、原来的IIS中的Session使用集群的Redis或MemCached替代。
2、替代原则客户端通过cookie存一个唯一标识的值。类似于上图ASP.NET_SessionID.
3、利用cookie建立对应关系,实现Session的存取。实现存取时原Session的KEY值+cookie中唯一标识,对应Cache中的KEY。
如情景①中需要使用session["UserID"]=18 Cache中对应key如下kutroxgrm2eslsmdwz2nemvbfSL516+UserID=18
2、替代原则客户端通过cookie存一个唯一标识的值。类似于上图ASP.NET_SessionID.
3、利用cookie建立对应关系,实现Session的存取。实现存取时原Session的KEY值+cookie中唯一标识,对应Cache中的KEY。
如情景①中需要使用session["UserID"]=18 Cache中对应key如下kutroxgrm2eslsmdwz2nemvbfSL516+UserID=18
知识点:
HTTP 是一种无状态协议。这意味着 Web 服务器会将针对页面的每个 HTTP 请求作为独立的请求进行处理。服务器不会保留以前的请求过程中所使用的变量值的任何信息。
ASP.NET通过会话标识符来判断是否是同一个客户端。简单点说就是依赖ASP.NET的SessionID值来判断,而默认情况下,
SessionID 值存储在 Cookie 中。
Session是依赖于cookie的
如图下图:
会话标识符
会话由一个唯一标识符标识,可使用 SessionID 属性读取此标识符。为 ASP.NET 应用程序启用会话状态时,将检查应用程序中每个页面请求是否有浏览器发送的 SessionID 值。如果未提供任何 SessionID 值,则 ASP.NET 将启动一个新会话,并将该会话的 SessionID 值随响应一起发送到浏览器。
默认情况下,SessionID 值存储在 Cookie 中。但也可以将应用程序配置为在“无 Cookie”会话的 URL 中存储 SessionID 值。
只要一直使用相同的 SessionID 值来发送请求,会话就被视为活动的。如果特定会话的请求间隔超过指定的超时值(以分钟为单位),则该会话被视为已过期。使用过期的 SessionID 值发送的请求将生成一个新的会话。
会话由一个唯一标识符标识,可使用 SessionID 属性读取此标识符。为 ASP.NET 应用程序启用会话状态时,将检查应用程序中每个页面请求是否有浏览器发送的 SessionID 值。如果未提供任何 SessionID 值,则 ASP.NET 将启动一个新会话,并将该会话的 SessionID 值随响应一起发送到浏览器。
默认情况下,SessionID 值存储在 Cookie 中。但也可以将应用程序配置为在“无 Cookie”会话的 URL 中存储 SessionID 值。
只要一直使用相同的 SessionID 值来发送请求,会话就被视为活动的。如果特定会话的请求间隔超过指定的超时值(以分钟为单位),则该会话被视为已过期。使用过期的 SessionID 值发送的请求将生成一个新的会话。
在了解其本质后,改造现有的Session,经过筛选实现下图功能。(选择最常用的功能。红框)
核心代码如下:
运行不了的,请屏蔽用,自行补全Memcached的存取。
public sealed class Session
{
#region
private System.Web.HttpContext current = null;//System.Web.HttpContext.Current;
private MemcachedContainer _cacheContainer = new MemcachedContainer();
private readonly string company = "CCTV_Session_";
private readonly string sessionId = "CCTV_SessionId"; //"ASP.NET_SessionId";// //"ASP.NET_SessionId"
private readonly string opengid = "openid";
public bool IsDebug = false;
public bool IsCustomKey = false;
#endregion
public Session()
{
current = System.Web.HttpContext.Current;
}
public Session(bool isDebug)
{
IsDebug = isDebug;
current = System.Web.HttpContext.Current;
}
public Session(int isCustomKey)
{
IsCustomKey = Convert.ToBoolean(isCustomKey);
current = System.Web.HttpContext.Current;
}
//public HttpSessionState Session;
//public int Timeout = 20 * 1000;
/// <summary>
/// 添加
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="timeOut"></param>
public void Add(string key, object value, int timeOut = 20*60)
{
// memcachedClient.get (current.Session.SessionID + key);
if (current == null)
{
if (IsDebug) Logs.WriteLogInfo("[Add]SessionID=current为null", "debug");
_cacheContainer.Add(company + key, value, timeOut);
}
else
{
string sessionValue = null;
if (IsCustomKey) //用户强制指定key值有重复危险慎用
{
if (IsDebug) Logs.WriteLogInfo("[当前创建-用户自定义]SessionID=" + sessionValue + "【完整版=】" + company + sessionValue + "_" + key + "【值:】" + value, "debug");
}
else if (!current.Request.Cookies.AllKeys.Contains(sessionId))//如果客户端不包含Session值那么创建一个SessionID
{
sessionValue = System.Guid.NewGuid().ToString("N");
current.Response.Cookies.Add(new HttpCookie(sessionId, sessionValue));//服务端返回
if (IsDebug) Logs.WriteLogInfo("[当前创建]SessionID=" + sessionValue + "【完整版=】" + company + sessionValue + "_" + key + "【值:】" + value, "debug");
}
else if (current.Request.Params.AllKeys.Contains(opengid))//添加微信公众号支持
{
sessionValue = current.Request.Params[opengid];
if (IsDebug) Logs.WriteLogInfo("[当前创建]opengid=" + sessionValue + "【完整版=】" + company + sessionValue + "_" + key + "【值:】" + value, "debug");
}
else
{
sessionValue = current.Request.Cookies[sessionId].Value;
if (IsDebug) Logs.WriteLogInfo("[客户端传来]SessionID=" + sessionValue + "[完整版=]" + company + sessionValue + "_" + key + "【值:】" + value, "debug");
}
_cacheContainer.Add(company + sessionValue + "_" + key, value, timeOut);
}
}
/// <summary>
/// 获取key值
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public object Get(string key)
{
try
{
if (current == null)
{
if (IsDebug) Logs.WriteLogInfo("[获取当前]SessionID=current为null", "debug");
return _cacheContainer.Get(company + key);
}
else
{
string sessionValue = "";
if (IsCustomKey) //用户强制指定key值有重复危险慎用
{
if (IsDebug) Logs.WriteLogInfo("[当前创建-用户自定义]SessionID=" + sessionValue + "[完整版=]" + company + sessionValue + "_" + key + "[值:]" + _cacheContainer.Get(company + sessionValue + "_" + key), "debug");
}
else if (current.Request.Cookies.AllKeys.Contains(sessionId))
{
sessionValue = current.Request.Cookies[sessionId].Value;
if (IsDebug) Logs.WriteLogInfo("[获取当前]SessionID=" + sessionValue + "[完整版=]" + company + sessionValue + "_" + key + "[值:]" + _cacheContainer.Get(company + sessionValue + "_" + key), "debug");
}
else if (current.Request.Params.AllKeys.Contains(opengid))//添加微信公众号支持
{
sessionValue = current.Request.Params[opengid];
if (IsDebug) Logs.WriteLogInfo("[获取当前]opengid=" + sessionValue + "[完整版=]" + company + sessionValue + "_" + key + "[值:]" + _cacheContainer.Get(company + sessionValue + "_" + key), "debug");
}
return _cacheContainer.Get(company + sessionValue + "_" + key);
}
}
catch (Exception ex)
{
return null;
}
}
/// <summary>
/// 移除
/// </summary>
/// <param name="name"></param>
public bool Remove(string name)
{
try
{
if (current == null)
{
if (IsDebug) Logs.WriteLogInfo("[移除当前]SessionID=current为null", "debug");
_cacheContainer.Remove(company + name);
return false;
}
else
{
string sessionValue = "";
if (IsCustomKey) //用户强制指定key值有重复危险慎用
{
if (IsDebug) Logs.WriteLogInfo("[移除当前]SessionID=" + sessionValue + "【完整版=】" + company + sessionValue + "_" + name, "debug");
}
else if (current.Request.Cookies.AllKeys.Contains(sessionId))
{
sessionValue = current.Request.Cookies[sessionId].Value;
if (IsDebug) Logs.WriteLogInfo("[移除当前]SessionID=" + sessionValue + "【完整版=】" + company + sessionValue + "_" + name, "debug");
}
else if (current.Request.Params.AllKeys.Contains(opengid))//添加微信公众号支持
{
sessionValue = current.Request.Params[opengid];
if (IsDebug) Logs.WriteLogInfo("[移除当前]opengid=" + sessionValue + "【完整版=】" + company + sessionValue + "_" + name, "debug");
}
_cacheContainer.Remove(company + sessionValue + "_" + name);
return true;
}
}
catch (Exception ex)
{
return false;
}
}
//
// 摘要:
// 按名称获取或设置会话值。
//
// 参数:
// name:
// 会话值的键名。
//
// 返回结果:
// 具有指定名称的会话状态值;如果该项不存在,则为 null。
public object this[string name]
{
get { return Get(name); }
set
{
Add(name, value);
}
}
}