概要的说,.NET内置的Session其实是借助于Cookie机制实现的。下面先解释为什么这么说,然后通过对比一个.NET内置的Session实例和一个手动模拟的Session机制(会借助于Cookie)来进一步验证。
为什么这么说
(1)首先,由于需要用到一些必要的Cookie相关知识,这里简要介绍:
Ø Cookie存放于客户端,所以东西是在别人手中,所以就不会很安全
Ø 由于浏览器可以设置清除、禁用Cookie,所以不可以将不可或缺的东西放到Cookie中,只能将可有可无的东西放到Cookie中。
Ø 服务器将Cookie写到客户端,之后当客户访问服务器(浏览器不变)该站点的任何内容时,request的信息中都会带上所有的Cookie的值。
Ø Cookie不能夸不同品牌的浏览器,互相访问(比如Chrom写的Cookie,IE不能使用)。
Ø 若不设置Cookie过期时间(Expires),则称为会话Cookie,表示这个Cookie的生命期为浏览器会话期间,关闭浏览器窗口,Cookie就消失,会话Cookie一般不存储在硬盘上而是保存在内存里;若设置了过期时间,浏览器就会把Cookie保存到硬盘上,关闭后再次打开浏览器,这些Cookie仍然有效直到超过设定的过期时间,或许应该这么说,如果设置了Expires,则这个Cookie的最长生命周期为Expires的值,如果Cookie已存满(个数和容量大小都有限制)则即使没到期,浏览器也会给删除。
(2)下面开始解释.NET的Session存放原理:
上面介绍了,ASP.NET为了保持和传递状态,一种机制就是上面的Cookie。写入Cookie的数据是存放在客户端的,不会很安全,所以需要将数据从客户端移到服务器端,即所谓的Session。但Session并不是将单纯的数据直接保存到服务器内存中,而是建立一个类似散列表的id-value映射表结构,value即指我们要保存的数据,id是由ASP.NET自动生成的一个编号(ASP.NET_SessionId),即:每当我们要写数据到Session时,ASP.NET就会生成一个编号(ASP.NET_SessionId,它是一个既不会重复,又不容易被找到规律以仿造的字符串),然后将编号作为数据的索引把二者进行绑定,然后会将这个编号以Cookie的形式保存到客户端,而将数据保留到服务器端的内存中,这样在客户端请求Session的同时会将Cookie中的ASP.NET_SessionId一同发送到服务器端,服务器就可以根据这个编号在自己的数据中进行检索,进而取出它的value值。这就是ASP.NET中的Session机制原理。可参考下面的草图:
实例说明-分析
下面做这么一个小例子:在登陆的时候将用户名写入Session,然后才能浏览站点内部的其它页面。如果没有登录,直接请求站点内部的page,则会检测Session,如果为空则跳转到登陆页面。
分别用.NET内置的Session实例和手动模拟的Session机制(会借助于Cookie)来实现。
当然,处理页面的很少的部分仍然采用NVelocity,所以首先添加它的DLL,并引用(参考前面两篇文章)。然后添加封装好的”渲染”代码, 如下的CommonHelper类:
public class CommonHelper
{
/// <summary>
/// 用data数据填充templateName模板,渲染生成html,返回
/// </summary>
/// <param name="templateName"></param>
/// <param name="data"></param>
/// <returns></returns>
public static string RenderHtml(string templateName, object data)
{
VelocityEngine vltEngine = new VelocityEngine();
vltEngine.SetProperty(RuntimeConstants.RESOURCE_LOADER, "file");
vltEngine.SetProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, System.Web.Hosting.HostingEnvironment.MapPath("~/templates"));//模板文件所在的文件夹
vltEngine.Init();
VelocityContext vltContext = new VelocityContext();
vltContext.Put("TemData", data);//设置参数,在模板中可以通过$data来引用
Template vltTemplate = vltEngine.GetTemplate(templateName);
System.IO.StringWriter vltWriter = new System.IO.StringWriter();
vltTemplate.Merge(vltContext, vltWriter);
string html = vltWriter.GetStringBuilder().ToString();
return html;
}
}
.NET内置的Session实例
先用我们平时用的Session实现上例,看看它的保存机制。
添加三个文件:Login3.html、Login3.ashx(做登录处理);TestLogin3.ashx(作为站点内部的其它页面)。代码如下:
Login3.html源码及运行界面:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>.net内置的Session测试</title>
</head>
<body>
<form action="../Login3.ashx" method="get">
用户名:<input type="text" name="username" />
<br />
密 码:<input type="text" name="password"/>
<br />
<input type="submit" name="login" value="提交" />
</form>
</body>
</html>
Login3.ashx:
public class Login3 : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/html";
string UserName = context.Request["username"];
string PassWord = context.Request["password"];
//登陆时,判断用户名是否正确(这里设置为admin),正确则保存用户名到Session,并跳转到TestLogin3.ashx
//否则清空输入框,页面不变
if (UserName == "admin")
{
//将用户名保存到.net内置Session中
context.Session["username"] = UserName;
//保存好用户名后进入TestLogin3.ashx页面
context.Response.Redirect("TestLogin3.ashx");
}
else
{
string html = CommonHelper.RenderHtml("Login3.html", null);
context.Response.Write(html);
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
TestLogin3.ashx:
public class TestLogin3 : IHttpHandler,IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/html";
//如果Session为null,则跳转到登陆页
if (context.Session == null)
{
context.Response.Redirect("Login3.ashx");
}
else
{
string username=(string)context.Session["username"];
//如果Session中的username对象为空或不存在则跳转到登陆页,否则将它显示出来
if (string.IsNullOrEmpty(username))
{
context.Response.Redirect("Login3.ashx");
}
else
{
context.Response.Write(username);
}
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
如下为填写用户名为admin点击“登录”捕获的数据,请求的是Login3.ashx,里面的处理是将用户名保存写入到了Session中,而可以看到响应标头中,有一个写Cookie的过程,而且Cookie的内容为ASP.NET_SessionId。
当Login3.ashx验证用户名正确后,立刻跳转到TestLogin3.ashx,如下图为跳转时捕获的数据,可以看到在请求的标头中携带有Cookie,而Cookie的值正是上面写Session时保存到客户端的ASP.NET_SessionId。
TestLogin3中的处理是,验证.Session["username"]是否存在,存在则取其值并显示。而验证是否存在和取其值的过程都是根据请求中的Cookie中的ASP.NET_SessionId来做的。
上例即可说明Session机制的原理。下面通过手动用Cookie来模拟Session来做相同的实例,以便大家理解的更深刻。
手动用Cookie来模拟Session机制
添加四个文件:Login2.html、Login2.ashx(做登录处理);TestLogin2.ashx(作为站点内部的其它页面)、SessionMgr.cs(模拟.net内部对Session对象的封装)。代码如下:
Login2.html与上面的Login3.html代码相同,只有title不同,代码:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>模拟.net内置的Session机制</title>
</head>
<body>
<form action="../Login2.ashx" method="get">
用户名:<input type="text" name="username" />
<br />
密 码:<input type="text" name="password"/>
<br />
<input type="submit" name="login" value="提交" />
</form>
</body>
</html>
SessionMgr.cs:
//自定义Session的操作类
public class SessionMgr
{
//Static在.net framework运行的时候一直存在
//自定义一个Dictionary类型的对象,来模拟.net内置的Session对象
private static Dictionary<Guid,string> testSession=new Dictionary<Guid,string>();
//写(自定义)session
public static void WriteSession(Guid id, string value)
{
testSession[id] = value;
}
//判断是否已经存在编号为id的(自定义)session
public static bool IsWriteSession(Guid id)
{
return testSession.Keys.Contains(id);
}
//获取编号为id的(自定义)session的value值
public static string Get(Guid id)
{
return testSession[id];
}
}
Login2.ashx:
public class Login2 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/html";
//取登陆时填写的用户名、密码
string UserName=context.Request["username"];
string PassWord = context.Request["password"];
//登陆时,判断用户名是否正确(这里设置为admin),正确则保存用户名到Session,并跳转到TestLogin3.ashx
//否则清空输入框,页面不变
if (UserName == "admin")
{
//关键点
Guid id = Guid.NewGuid();//生成一个session编号,Guid可生成一个全球唯一的编码,来模拟.net内置Session的ASP.NET_SessionId
SessionMgr.WriteSession(id, UserName);//写入(自定义的)session,将UserName与生成的id绑定(借助于SessionMgr类)
HttpCookie cookie1 = new HttpCookie("sessionid", id.ToString());//只将id保存到cookie中,并未保存对应的数据(UserName)
context.Response.SetCookie(cookie1);
context.Response.Redirect("TestLogin2.ashx");
}
else
{
string html = CommonHelper.RenderHtml("Login2.html", null);
context.Response.Write(html);
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
TestLogin2.ashx:
public class TestLogin2 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/html";
HttpCookie cookie = context.Request.Cookies["sessionid"];
//请求此页时,如果cookie为null则跳转到登陆页
//如果不为空,则取出cookie的值作为一个Guid编号
if (cookie == null)
{
context.Response.Redirect("Login2.ashx");
}
else
{
Guid id = new Guid(cookie.Value);
//判断服务器端的Dictionary中是否包含编号为id的对象
//如果包含则取出其value,并显示
//否则跳转到登陆页
if (SessionMgr.IsWriteSession(id))
{
string value = SessionMgr.Get(id);
context.Response.Write(value);
}
else
{
context.Response.Write("<script>alert('没有登录!');</script>");
context.Response.Redirect("Login2.ashx");
}
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
上例中用一个Dictionary<id,value>对象来模拟Session对象,用它的id(Guid)来模拟.NET自动生成的编号(ASP.NET_SessionId),用它的value来模拟要写入Session的数据。
浏览器禁用Cookie问题
上面介绍Cookie时谈到,浏览器可以设置清除、禁用Cookie。而刚刚谈到的Session机制是依赖于Cookie的。那么按上面的意思,当禁用Cookie时就意味着ASP.NET_SessionId 无法回传服务器,Session也就无法使用。是否是这样?答案肯定是否,应对这种情况,经常用两种方法:
URL地址重写:
即将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。
表单隐藏字段
服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。例如:
总结
之前写过相关的文章,完全是理论的学习,现在所有的东西都可以通过写代码和浏览器调试来验证自己的想法。上面是目前阶段自己的理解,希望能帮助大家理解ASP.NET的Session机制。