作者:陈广 日期:2018-12-5
Session,在计算机中,尤其网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。
这段定义听着怎么感觉那么像 Cookie 呢?没错,Session 和 Cookie 类似,只是 Cookie 是客户端机制,而 Session 是服务器机制,而且,一般情况下,Session 是和 Cookie 配合使用的。
Session 与 Cookie 是如何配合的呢?下面大概介绍一下?
理解一个知识点的最好办法就是运行一个例子,查看它是如何工作的。下面我们就通过例子来查看 Session 的运行流程。
新建一个名为 Session 的文件夹,在右键菜单上选择【Open with Code】打开此文件夹。按下【Ctrl + ~】快捷键打开终端,输入如下命令:
dotnet new empty
创建项目完成后,首先关掉 HTTPS,打开 Properties 文件夹下的 launchSettings.json 文件,将sslPort
项的值更改为0
以关闭 HTTPS。将applicationUrl
项的值更改如下:
"applicationUrl": "http://localhost:5000",
ASP.NET Core 已经内置了完善的 Session 服务,我们只需添加相应的服务就可以调用了。更改 Startup.cs 文件代码如下:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Session
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSession();
app.UseMvcWithDefaultRoute();
}
}
}
要使用 Session,请先在ConfigureServices
方法中使用services.AddSession()
,然后在Configure
方法中使用app.UseSession()
。这里需要注意的是app.UseSession()
一定要放在app.UseMvc()
之前调用,否则会引发InvalidOperationException
异常。
接下来添加控制器。在项目根目录中添加一个 Controllers 文件夹,并在其中新建一个 HomeController.cs 文件,输入代码如下:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
namespace Session.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{ //设置一个 Session
HttpContext.Session.SetInt32("Item1", 2018);
return View();
}
public IActionResult Second()
{ //获取指定键的 Session 值
int? num = HttpContext.Session.GetInt32("Item1");
return View(num);
}
public IActionResult Three()
{ //获取指定键的 Session 值
int? num=HttpContext.Session.GetInt32("Item1");
return View(num);
}
}
}
此程序结构和上篇讲 Cookie 文章的程序类似,也是三个 action 方法对应三个视图。不同之处就是这里是设置并读取 Session 的值。
在项目根目录下新建 Views 文件夹。
在 Views 文件夹下新建一个 _ViewImports.cshtml 文件,并输入如下代码以加入标签助手引用:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
在 Views 文件夹下新建 Home 文件夹。然后在 Home 文件夹下新建 Index.cshtml 文件,并输入如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<a asp-action="Second">跳转至第二个页面</a>
</body>
</html>
此视图为根视图,里面只存在一个跳转至第二个页面的链接。它所对应的 action 方法为Index。
在 Views/Home 文件夹下新建 Second.cshtml 文件,输入如下代码:
@model int
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>这是第二个页面</h2>
服务器读取的 Session 值:
<h3>@Model</h3>
<a asp-action="Three">跳转到第三个页面</a>
</body>
</html>
此视图为第二个页面所对应的视图文件,第一句@model
语句表明此视图将接受由 action 方法传递过来的int
对象,并在后面通过Model
对象调用。action 方法的传递方式可通过查看 HomeController.cs 文件下的Second()方法的返回语句:
在 Views/Home 文件夹下新建 Three.cshtml 文件,输入如下代码:
@model int
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>这是第三个页面</h2>
服务器读取的 Session 值:
<h3>@Model</h3>
</body>
</html>
此视图为第三个页面。
运行程序,在浏览器中按【F12】打开开发者工具。查看响应标头,可以看到服务器在响应中加入了一个 Cookie,键为.AspNetCore.Session
,如下图所示。
图中 Set-Cookie 部分全文如下:
Set-Cookie: .AspNetCore.Session=CfDJ8O%2FaneK7Ow1Fm53GRHD%2F5J6iNuagtIrXNUIM4aAvEnR%2FhWchlgkeweEveUgDg8aPvAXGjGcd4xFleYzpi043Olf2ylc4Q0tdX8lKcemR7NNgV2XIts%2BqLEIqeGb5hTrZgzDMSEkZq%2BYTlnrfC61BTG0dkgYyZ15K5NKbTaXR1Zmy; path=/; samesite=lax; httponly
.AspNetCore.Session
的值已经过 ASP.NET Core 加密,我们无法得知,它应该就是我们之前所说的 Session Id。
接下来点击页面【跳转到第二个页面】链接,然后查看【请求标头】,我们看到请求 header 中已发送了 Cookie 项,键值依然为.AspNetCore.Session
,这正是刚才服务器发送过来的 Cookie。如下图所示:
其中 Cookie 值全文如下:
Cookie: .AspNetCore.Session=CfDJ8O%2FaneK7Ow1Fm53GRHD%2F5J6g0%2FZaeJFvUf5bHsP6WQReV0s%2F6%2Bxl7cGRSiMXJS4kX0wMnU8NJi3q6bFwQmAQsbFAhhYPtN0BUAFHdSjC4c840X8KCT3gz8OmKNxtV4xHK%2FbMHiGQF0LD1d5JsgQPn2cg2LvUuR8klg%2FivvRlU6DX
我们看到,返回去的值已有了改变,但从页面中服务器读出来的 Session 值来看,里面存放的值是没有改变的。
接下来继续点击页面上的【跳转到第三个页面】链接,查看开发者工具,发现跳转还是携带了相同的 Cookie,并且服务器读取了 Session 值并返回页面显示。
由这个例子我们可以推断出。当浏览器第一次请求服务器时,服务器通过HttpContext.Session.SetInt32
设置了 Session 值,并向浏览器通过 Set-Cookie 返回一个 Session Id,此 Id 由 .AspNetCore.Session
指定。在浏览器的一系列后续请求中,浏览器会通过 Cookie 中将此 Id 一起发送给服务器。然后服务器通过此 Id 找到相应的 Session,并读取其中的值返回给客户端显示。
在配置会话状态服务时,我们可以设置各种属性:
InfiniteTimeSpan
禁用此超时。下面更改示例,以演示如何设置 Session 属性。更改 Startup.cs 文件代码如下:
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Session
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSession(options =>
{
options.Cookie.Name = ".Iotxfd.Session"; //更改 Cookie 名称
options.IdleTimeout = TimeSpan.FromSeconds(10); //10秒后 Session 失效
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSession();
app.UseMvcWithDefaultRoute();
}
}
}
注意,新增了一个命名空间,更改了ConfigureServices
方法内的代码。这里,我们将 Cookie 名称进行了更改,并设置了超时时间。
运行程序,使用开发者工具查看 Cookie,可以看到 Cookie 的名称已被更改为'.Iotxfd.Session',如下图所示:
第一个页面打开 10 秒内跳转到第二个页面还可以读取 Session 值 2018
。再等 10 以后点击第三个页面则读取出的 Session 值就变为 0 了,说明 Session 过了 10 秒后就失效了。
实验做完后,将改码改回原样,继续下面的实验。
我们在写程序时,通过代码提示可以看到 Session 只有三个设置值的方法:Set
、SetInt32
和SetString
。也就是说 Session 只能存储字节数组、整数和字符串。如果要保存其他数据类型或自定义类该怎么办泥?微软给出的解决方案是使用 JSON。在 C# 中操作 JSON 可以使用 Newtonsoft 程序包,Newtonsoft 异常强大,可以直接将复杂对象序列化为 JSON 格式,之后可以将此序列化后的 JSON 字符串反序列化为 C# 可以使用的对象。那么,我们在写 Session 时就可以通过 Newtonsoft 将复杂对象序列化为字符串进行存储,在读 Session 时将读取的字符串反序列化为 C# 对象。这个过程需要使用扩展方法来实现,以使我们可以直接在HttpContext.Session
属性中调用。
下面演示如何在 Session 中存储日期。更改 HomeController.cs 文件如下:
using System;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
namespace Session.Controllers
{
//扩展方法
public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{ //将对象 T 写入 Session
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T Get<T>(this ISession session, string key)
{ //从 Session 中读对象 T
var value = session.GetString(key);
return value == null ? default(T) :
JsonConvert.DeserializeObject<T>(value);
}
}
public class HomeController : Controller
{
public IActionResult Index()
{ //设置一个 Session
HttpContext.Session.Set<DateTime>("Item", DateTime.Now);
return View();
}
public IActionResult Second()
{
DateTime time = HttpContext.Session.Get<DateTime>("Item");
return View(time);
}
public IActionResult Three()
{
DateTime time = HttpContext.Session.Get<DateTime>("Item");
return View(time);
}
}
}
注意:如果是 .NET Core 2.0 及以前版本,需要引入 Newtonsoft NuGet 程序包。2.1 版本之后,.NET Core 已经内置了 Newtonsoft。具说 .NET Core 3.0 将移除内置 Newtonsoft,改用高性能内置 JSON APIs,使用
Span<T>
实现,变化太快了。
接下来将 Second.cshtml 的第一句代码:
@model int
改为
@model DateTime
接下来将 Three.cshtml 的第一句代码:
@model int
改为
@model DateTime
运行程序,我们看到,现在 Session 已经可以存储时间了,如下图所示。
为了加深对 Session 的理解,下面我们来做一个常见的购物车应用。
新建一个名为 ShoppingCart 的文件夹,在右键菜单上选择【Open with Code】打开此文件夹。按下【Ctrl + ~】快捷键打开终端,输入如下命令:
dotnet new empty
创建项目完成后,首先关掉 HTTPS,打开 Properties 文件夹下的 launchSettings.json 文件,将sslPort
项的值更改为0
以关闭 HTTPS。将applicationUrl
项的值更改如下:
"applicationUrl": "http://localhost:5000",
下面添加 Session 服务,更改 Startup.cs 文件代码如下:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Session
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSession();
app.UseMvcWithDefaultRoute();
}
}
}
接下来添加控制器。在项目根目录中添加一个 Controllers 文件夹,并在其中新建一个 HomeController.cs 文件,输入代码如下:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace ShoppingCart.Controllers
{
public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{ //将对象 T 写入 Session
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T Get<T>(this ISession session, string key)
{ //从 Session 中读对象 T
var value = session.GetString(key);
return value == null ? default(T) :
JsonConvert.DeserializeObject<T>(value);
}
}
public class HomeController : Controller
{
static List<string> products = new List<string>()
{ //商品数据
"手机","电脑","苹果","饼干","鼠标",
"钢笔","裤子","相机","香蕉","雨伞"
};
public IActionResult Index()
{ //从 Session 中获取购物车商品
List<string> cart = HttpContext.Session.Get<List<string>>("Cart");
if (cart != null)
{ //将购物车数据通过 ViewData 传递给页面
ViewData["Cart"]=cart.ToArray();
}
return View(products.ToArray());
}
public IActionResult AddToCart(int id)
{
List<string> cart = HttpContext.Session.Get<List<string>>("Cart");
if (cart != null)
{ //从 Session 中获取购物车商品
if (!cart.Contains(products[id]))
{ //如果商品不在购物车中,则加进去
cart.Add(products[id]);
HttpContext.Session.Set<List<string>>("Cart", cart);
}
}
else
{ //如果还未创建购物车,则创建它
cart = new List<string>();
cart.Add(products[id]);
HttpContext.Session.Set<List<string>>("Cart", cart);
}
return RedirectToAction(nameof(Index)); //重定向至首页
}
}
}
为了省事,Session 的扩展方法直接放在控制器里了,程序的模型层也省了,直接使用一个List
存放商品数据。这样做只是为了专注于 Session,尽量简单,排除所有干扰。这些并不是好的习惯,实际开发中千万不要这样做。
在Index
action 方法中,由于View
方法参数已经用于传递商品数据,所以只能通过ViewData
传递购物车数据。AddToCart
action 方法用于在购物车中添加商品。我们可以看到,购物车里的商品实际上是存放在 Session 中的,购物车处理完毕后会重定向至首页以实时更新购物车内容。
在项目根目录下新建 Views 文件夹。
在 Views 文件夹下新建 Home 文件夹。然后在 Home 文件夹下新建 Index.cshtml 文件,并输入如下代码:
@model string[]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>IOT小分队购物网站</h1>
<ul>
@for (int i = 0; i < Model.Length; i++)
{
<li>
@{
string s=$"/Home/AddToCart/{i}";
}
<a href=@s>@Model[i]</a>
</li>
}
</ul>
<hr>
<h2>购物车:</h2>
<ul>
@if(ViewData["Cart"] != null)
{
@foreach(var item in ViewData["Cart"] as string[])
{
<li>
@item
</li>
}
}
</ul>
</body>
</html>
此页面分为两部分,上部分为商品列表,通过 action 方法中返回的模型数据获取。下半部分为购物车,通过ViewData
获取。
其实,将商品加入购物车应使用 POST 方法提交数据,为了省事,我直接将商品索引使用 GET 方法通过 URL 传递给服务器了。要怪只能怪 Razor 太强大,这样写都可以,几句代码搞定。
运行程序,点击商品,可以看到,所点击的商品会实时在下方购物车中列出,如图3所示:
呵呵,这应当是史上最穷购物车了,什么功能都木有。你要功能比较完善的购物车也有,请在本网站找自由男写的《Pro ASP.NET Core MVC 2(第7版)》这本书,里面的运动商店项目有购物车功能,足够复杂。
虽然程序功能简单,但它还是可以帮助我们做一些实验以进一步了解 Session 机制的。
对于大的购物网站来说,比如京东,购物车功能是存放在 Cookie 里面的。这么多人访问,使用 Session 来存放并不现实。京东在不登录的情况下在购物车中加入商品,即使等到第二天,商品仍然存在。清空浏览器 Cookie,购物车商品就空了,这佐证了以上说法。
;