概要的说,.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; } }
先用我们平时用的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来做相同的实例,以便大家理解的更深刻。
添加四个文件: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。而刚刚谈到的Session机制是依赖于Cookie的。那么按上面的意思,当禁用Cookie时就意味着ASP.NET_SessionId 无法回传服务器,Session也就无法使用。是否是这样?答案肯定是否,应对这种情况,经常用两种方法:
URL地址重写:
即将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。
表单隐藏字段
服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。例如:
之前写过相关的文章,完全是理论的学习,现在所有的东西都可以通过写代码和浏览器调试来验证自己的想法。上面是目前阶段自己的理解,希望能帮助大家理解ASP.NET的Session机制。
原文:http://blog.csdn.net/wang379275614/article/details/18801823