首页 > 编程语言 >ASP.NET Core 6框架揭秘实例演示[35]:利用Session保留语境

ASP.NET Core 6框架揭秘实例演示[35]:利用Session保留语境

时间:2022-09-06 08:22:36浏览次数:82  
标签:Core 存储 ASP 会话 Session Cookie Key ID

客户端和服务器基于HTTP的消息交换就好比两个完全没有记忆能力的人在交流,每次单一的HTTP事务体现为一次“一问一答”的对话。单一的对话毫无意义,在在同一语境下针对某个主题进行的多次对话才会有结果。会话的目的就是在同一个客户端和服务器之间建立两者交谈的语境或者上下文,ASP.NET Core利用一个名为SessionMiddleware的中间件实现了会话。本篇提供了几个简单的实例来演示如何在一个ASP.NET Core应用中利用会话来存储用户的状态。(本文提供的示例演示已经同步到《ASP.NET Core 6框架揭秘-实例演示版》)。

[S2301]设置和提取会话状态(源代码
[S2302]查看存储的会话状态(源代码
[S2303] 查看Cookie(源代码

[S2301]设置和提取会话状态

每个会话都有一个被称为Session Key的标识(但不是唯一标识),会话状态以一个数据字典的形式将Session Key保存在服务端。当SessionMiddleware中间件在处理会话的第一个请求时,它会创建一个Session Key,并据此创建一个独立的数据字典来存储会话状态。这个Session Key最终以Cookie的形式写入响应并返回客户端,客户端在每次发送请求时会自动附加这个Cookie,那么应用程序能够准确识别会话并成功定位存储会话状态的数据字典。下面我们利用一个简单的实例来演示会话状态的读写。ASP.NET应用在默认情况下会利用分布式缓存来存储会话状态。我们采用基于Redis数据库的分布式缓存,所以需要添加针对NuGet包“Microsoft.Extensions.Caching.Redis”的依赖。下面的演示程序调用了AddDistributedRedisCache扩展方法添加了基于DistributedRedisCache的服务注册,SessionMiddleware中间件则通过调用UseSession扩展方法进行注册。

using System.Text;

var builder = WebApplication.CreateBuilder();
builder.Services
    .AddDistributedRedisCache(options => options.Configuration = "localhost")
    .AddSession();
var app = builder.Build();
app.UseSession();
app.MapGet("/{foobar?}", ProcessAsync);
app.Run();

static async ValueTask<IResult> ProcessAsync(HttpContext context)
{
    var session = context.Session;
    await session.LoadAsync();
    string sessionStartTime;
    if (session.TryGetValue("__SessionStartTime", out var value))
    {
        sessionStartTime = Encoding.UTF8.GetString(value);
    }
    else
    {
        sessionStartTime = DateTime.Now.ToString();
        session.SetString("__SessionStartTime", sessionStartTime);
    }

    var html = $@"
<html>
    <head><title>Session Demo</title></head>
    <body>
        <ul>
            <li>Session ID:{session.Id}</li>
            <li>Session Start Time:{sessionStartTime}</li>
            <li>Current Time:{DateTime.Now}</li>
        <ul>
    </body>
</html>";
    return Results.Content(html, "text/html");
}

我们针对路由模板“/{foobar?}”注册了一个终结点,后者的处理器指向ProcessAsync方法。该方法当前HttpContext上下文中获取表示会话的Session对象,并调用其TryGetValue方法获取会话开始时间,这里使用的Key为“__SessionStartTime”。由于TryGetValue方法总是以字节数组的形式返回会话状态值,所以我们采用UTF-8编码转换成字符串形式。如果会话开始时间尚未设置,我们会调用SetString方法采用相同的Key进行设置。我们最终生成一段用于呈现Session ID和当前实时时间HTML,并封装成返回的ContentResult对象。程序启动之后,我们利用Chrome和IE访问请求注册的终结点,从图1可以看出针对Chrome的两次请求的Session ID和会话状态值都是一致的,但是IE中显示的则不同。

image

图1 以会话状态保存的“会话开始时间”

[S2302]查看存储的会话状态

会话状态在默认情况下采用分布式缓存的形式来存储,而我们的实例采用的是基于Redis数据库的分布式缓存,那么会话状态会以什么样的形式存储在Redis数据库中的呢?由于缓存数据在Redis数据库中是以散列的形式存储的,所以我们只有知道具体的Key才能知道存储的值。缓存状态是基于作为会话标识的Session Key进行存储的,它与Session ID具有不同的值,到目前为止我们不能使用公布出来的API来获取它,但可以利用反射的方式来获取Session Key。在默认情况下,表示Session的是一个DistributedSession对象,它通过如下所示的字段_sessionKey表示这个用来存储会话状态的Session Key。

public class DistributedSession : ISession
{
    private readonly string _sessionKey;
    ...
}

接下来我们对上面演示的程序做简单的修改,从而使Session Key能够呈现出来。如下面的代码片段所示,我们可以采用反射的方式得到代表当前会话的DistributedSession对象的_sessionKey字段的值,并将它写入响应HTML文档的主体内容中。

static async ValueTask<IResult> ProcessAsync(HttpContext context)
{
    var session = context.Session;
    await session.LoadAsync();
    string sessionStartTime;
    if (session.TryGetValue("__SessionStartTime", out var value))
    {
        sessionStartTime = Encoding.UTF8.GetString(value);
    }
    else
    {
        sessionStartTime = DateTime.Now.ToString();
        session.SetString("__SessionStartTime", sessionStartTime);
    }

    var field = typeof(DistributedSession).GetTypeInfo().GetField("_sessionKey", BindingFlags.Instance | BindingFlags.NonPublic)!;
    var sessionKey = field.GetValue(session);

    var html = $@"
<html>
    <head><title>Session Demo</title></head>
    <body>
        <ul>
            <li>Session ID:{session.Id}</li>
            <li>Session Start Time:{sessionStartTime}</li>
            <li>Session Key:{sessionKey}</li>
            <li>Current Time:{DateTime.Now}</li>
        <ul>
    </body>
</html>";
    return Results.Content(html, "text/html");
}

按照同样的方式启动应用后,我们使用浏览器访问目标站点得到的输出结果如图2所示,可以看到,Session Key的值被正常呈现出来,它是一个不同于Session ID的GUID。

image

图2 呈现当前会话的Session Key

如果有这个保存当前会话状态的Session Key,我们就可以按照图3所示的方式采用命令行的形式将存储在Redis数据库中的会话状态数据提取出来。当会话状态在采用默认的分布式缓存进行存储时,整个数据字典(包括Key和Value)会采用预定义的格式序列化成字节数组,这基本上可以从图3体现出来。我们还可以看出基于会话状态的缓存默认采用的是基于滑动时间的过期策略,默认采用的滑动过期时间为20分(12 000 000 000纳秒)。

image

图3 存储在Redis数据库中的会话状态

[S2303] 查看Cookie

虽然整个会话状态数据存储在服务端,但是用来提取对应会话状态数据的Session Key需要以Cookie的形式由客户端来提供。如果请求没有以Cookie的形式携带Session Key,SessionMiddleware中间件就会将当前请求视为会话的第一次请求。在此情况下,它会生成一个GUID作为Session Key,并最终以Cookie的形式返回客户端。

HTTP/1.1 200 OK
...
Set-Cookie:.AspNetCore.Session=CfDJ8CYspSbYdOtFvhKqo9CYj2vdlf66AUAO2h2BDQ9%2FKoC2XILfJE2bk
IayyjXnXpNxMzMtWTceawO3eTWLV8KKQ5xZfsYNVlIf%2Fa175vwnCWFDeA5hKRyloWEpPPerphndTb8UJNv5R68bGM8jP%2BjKVU7za2wgnEStgyV0ceN%2FryfW; path=/; httponly

如上所示的代码片段是响应报头中携带Session Key的Set-Cookie报头在默认情况下的表现形式。可以看出Session Key的值不仅是被加密的,更具有一个httponly标签以防止Cookie值被跨站读取。在默认情况下,Cookie采用的路径为“/”。当我们使用同一个浏览器访问目标站点时,发送的请求将以如下形式附加上这个Cookie。

GET http://localhost:5000/ HTTP/1.1
...
Cookie: .AspNetCore.Session=CfDJ8CYspSbYdOtFvhKqo9CYj2vdlf66AUAO2h2BDQ9%2FKoC2XILfJE2bkIayyjXnXpNxMzMtWTceawO3eTWLV8KKQ5xZfsYNVlIf%2Fa175vwnCWFDeA5hKRyloWEpPPerphndTb8UJNv5R68bGM8jP%2BjKVU7za2wgnEStgyV0ceN%2FryfW

除了Session Key,前面还提到了Session ID,读者可能不太了解两者具有怎样的区别。Session Key和Session ID是两个不同的概念,上面演示的实例也证实了它们的值其实是不同的。Session ID可以作为会话的唯一标识,但是Session Key不可以。两个不同的Session肯定具有不同的Session ID,但是它们可能共享相同的Session Key。当SessionMiddleware中间件接收到会话的第一个请求时,它会创建两个不同的GUID来分别表示Session Key和Session ID。其中Session ID将作为会话状态的一部分被存储起来,而Session Key以Cookie的形式返回客户端。

会话是具有有效期的,会话的有效期基本决定了存储的会话状态数据的有效期,默认过期时间为20分钟。在默认情况下,20分钟之内的任意一次请求都会将会话的寿命延长至20分钟后。如果两次请求的时间间隔超过20分钟,会话就会过期,存储的会话状态数据(包括Session ID)会被清除,但是请求携带可能还是原来的Session Key。在这种情况下,SessionMiddleware中间件会创建一个新的会话,该会话具有不同的Session ID,但是整个会话状态依然沿用这个Session Key,所以Session Key并不能唯一标识一个会话。

标签:Core,存储,ASP,会话,Session,Cookie,Key,ID
From: https://www.cnblogs.com/artech/p/inside-asp-net-core-6-35.html

相关文章

  • C#/.NET/.NET Core优秀项目框架推荐
    前言:为.NET开源者提供的一个推荐自己优秀框架的地址,大家可以把自己的一些优秀的框架,或者项目链接地址存到在这里,提供给广大.NET开发者们学习(排名不分先后)。Github项......
  • Session认证机制与JWT认证机制
    一、什么是身份认证?身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。日常生活中的身份认证随处可见,例如:高铁的验票乘车,手......
  • IIS 部署ASPNET MVC5项目问题
    如题,部署ASPNETMVC5项目到IIS上的时候遇到一个奇葩的问题,首先项目代码本身应该是没问题的,因为我已经部署成功到一台服务器了,这一台是新的服务器,然后遇到了这个问题。在部......
  • ASP.NET总结C#中7种获取当前路径的方法
    1.System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName -获取模块的完整路径。 2.System.Environment.CurrentDirectory -获取和设置当前目录(该进程......
  • Asp.net Mvc 区域路由默认设置
    publicclassRouteConfig{publicstaticvoidRegisterRoutes(RouteCollectionroutes){routes.IgnoreRoute("{resource}.axd/......
  • .Net Core&RabbitMQ优先级队列
    优先级队列消息除了有生命周期长短,也有紧急与非紧急之分,承载了具有优先级消息的队列则为优先级队列。队列优先级设置为消息设置优先级前,队列需要先具备优先级的能力,队......
  • 【转】spring-session-data-redis核心原理
    这个组件的核心本质就是在实现单点登录SSO问题,将用户的登录session信息从原来的存储在jvm中转移到redis中去,微服务架构下每个应用接到请求都不会从自己的节点解析用户登录......
  • c# 框架系列 ———— EFCore 模型篇 [一]
    前言简单介绍一下EfCore的模型篇正文内容来源:配置模型配置模型的方式,一种是fluentapi还一种是属性的方式。publicclassBlog{ publicintBlogId{get;set;......
  • 1.try-except 2. if 0<=score<=1.0 3. print加引号是为什么
    Assignment3.3Writeaprogramtopromptforascorebetween0.0and1.0.Ifthescoreisoutofrange,printanerror.Ifthescoreisbetween0.0and1.0,pri......
  • ASP.NET Core源码,数据结构和算法,
    ASP.NETCore源码:https://github.com/dotnet/aspnetcore#ASP.NETCorehttps://github.com/dotnet/runtime#extend扩展库https://github.com/aspnet/KestrelHttpServer ......