为什么我的会话状态在ASP.NET Core中不工作了?

简介: 为什么我的会话状态在ASP.NET Core中不工作了? 原文:Why isn't my session state working in ASP.NET Core? Session state, GDPR, and non-essential cookies 作者:Andrew Lock 译文:https://www.cnblogs.com/lwqlun/p/10526380.html 译者:Lamond Lu 在本篇博客中,我将描述一个关于会话状态(Session State)的问题, 这个问题我已经被询问了好几次了。

为什么我的会话状态在ASP.NET Core中不工作了?

原文:Why isn't my session state working in ASP.NET Core? Session state, GDPR, and non-essential cookies
作者:Andrew Lock
译文:https://www.cnblogs.com/lwqlun/p/10526380.html
译者:Lamond Lu

65831-20190314072844916-1616505992.jpg

在本篇博客中,我将描述一个关于会话状态(Session State)的问题, 这个问题我已经被询问了好几次了。这个问题的场景如下:

  • 创建一个新的ASP.NET Core应用程序
  • 一个用户在会话状态中设置了一个字符串值,例如HttpContext.Session.SetString("theme", "Dark");
  • 在下一次请求中,尝试从会话中读取这个自字符串的值HttpContext.Session.GetString("theme");, 但是得到的结果却是null!
  • “额,这个愚蠢的框架不工作了”(╯°□°)╯︵ ┻━┻

这个问题的原因是ASP.NET Core 2.1中引入的GDPR功能与会话状态互相影响了。在本篇博客中,我将描述为什么你会看到这种行为,以及一些处理它的方法。

GDPR中ASP.NET Core 2.1中引入的一个特性,如果你使用NET Core 1.x或2.0版本,你将不会遇到这个问题。但是请记住,自2019年6月27起,1.x版本即将失去支持,2.0版本已经不受支持了,因此你应该考虑升级到2.1及以上版本。

说明:

  • 《通用数据保护条例》(General Data Protection Regulation,简称GDPR)为欧洲联盟的条例,前身是欧盟在1995年制定的《计算机数据保护法》。
  • 2018年5月25日,欧洲联盟出台《通用数据保护条例》。

ASP.NET Core中的会话状态#

就像我前面所说的,如果你使用的是ASP.NET Core 2.0及以前的版本,你不会遇到这个问题。这里我将借助ASP.NET Core 2.0展示一下预期的行为,以便说明遇到这个问题的人期望的会话状态行为。然后我将在ASP.NET Core 2.2中创建等效的应用程序,并显示会话状态不再起作用了。

什么是会话状态?#

会话状态是一种可以回溯到ASP.NET(非核心)的功能,你可以使用它为浏览站点的用户存储和检索服务器端的值。 会话状态经常在ASP.NET应用程序中广泛使用,但经常由于一些原因而出现问题,主要是性能和可伸缩性。

ASP.NET Core中的你应该把会话状态看作针对每用户的缓存。 从技术角度来看,ASP.NET Core中的会话状态的功能需要2个独立的部分来完成:

  • 一个Cookie。 用来指定每个用户的唯一ID(Session ID)
  • 一个分布式缓存。用来存储与每个用户唯一ID关联的数据项

在一般的情况下,我会尽量避免使用会话状态,使用会话状态可能会有很多陷阱,如果不注意,就会引起一起不必要的问题。例如:

  • 会话是针对每个浏览器的,而不是每个登录用户的
  • 会话结束的时候,应该删除会话Cookie,但可能不会
  • 如果会话中没有任何值,它将会被删除,并重新生成一个新的会话ID
  • 本文中即将描述的GDPR问题

这里我们讲解了什么是会话状态,以及其工作的原理。在下一节中,我将创建一个小程序,这个小程序会使用会话状态存储你访问过的页面,然后在首页上显示该列表。

在ASP.NET Core 2.0项目中使用会话状态#

为了说明ASP.NET Core 2.0版本和2.1以上版本的行为变化,我将先创建一个ASP.NET Core 2.0项目,因为我的电脑上安装了许多.NET Core SDK, 这里我将使用2.0 SDK(版本号2.1.202)来构建一个2.0项目模板。

这里我们首先创建一个global.json, 将当前app目录的SDK版本固定为2.1.202版本。

 
 
Copy
dotnet new globaljson --sdk-version 2.1.202

然后使用dotnet new命令创建一个新的ASP.NET Core MVC 2.0应用程序

 
 
Copy
dotnet new mvc --framework netcoreapp2.0

会话状态默认情况下是没有启用的,所以这里你需要先添加必要的服务。我们修改Startup.cs文件ConfigureServices方法来添加会话服务。默认情况下,ASP.NET Core将使用内存来存储会话信息,这对于测试来说很友好,但是生产环境中可能就需要替换为其他方式。

 
 
Copy
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSession(); // add session }

当然,只添加服务是没有用的,我们还需要在管道中注册会话中间件。只有注册在会话中间件之后的中间件才可以访问会话状态,所以你需要将会话中间件放在MVC中间件之前。

 
 
Copy
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ...其他配置 app.UseSession(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }

对于这个简单的例子,我将使用会话密钥"actions"来存储并读取一个字符串类型的会话值,这个会话值中会保存你访问过的所有页面。当你在不同的页面间浏览时,我们会将你访问过的页面以分号分隔的形式保存在"actions"会话值中。现在我们更新HomeController的代码:

 
 
Copy
public class HomeController : Controller { public IActionResult Index() { RecordInSession("Home"); return View(); } public IActionResult About() { RecordInSession("About"); return View(); } private void RecordInSession(string action) { var paths = HttpContext.Session.GetString("actions") ?? string.Empty; HttpContext.Session.SetString("actions", paths + ";" + action); } }

注意:Session.GetString(key)Microsoft.AspNetCore.Http命名空间中的一个扩展方法。

最后,我们修改Index.cshtml页面的代码如下,在页面中显示当前"actions"的会话值

 
 
Copy
@using Microsoft.AspNetCore.Http @{ ViewData["Title"] = "Home Page"; } <div> @Context.Session.GetString("actions") </div>

如果你现在运行应用程序并浏览几次,你将看到会话页面访问历史列表的构建。 在下面的示例中,我访问了主页三次,关于页面两次:

65831-20190314072912468-39064354.png

如果查看当前页面关联的Cookie信息,你就会看到一个名为.AspNetCore.Session的Cookie, 它的值就是一个加密会话ID, 如果你删除这个Cookie, 你将会看到"actions"的值被重置,页面访问历史列表丢失。

65831-20190314072924244-327044147.png

这种会话状态的行为就是大部分人所期望的,所以这里没有问题。但是当你使用ASP.NET Core 2.1/2.2版本创建相同项目之后,情况就不一样了。

在ASP.NET Core 2.2项目中使用会话状态#

为了创建ASP.NET Core 2.2应用程序,我使用了几乎相同的行为,但这次我没有固定SDK。 我安装了ASP.NET Core 2.2 SDK(2.2.102),因此以下命令会生成一个ASP.NET Core 2.2 MVC应用程序:

 
 
Copy
dotnet new mvc

这里你依然需要显示注册会话服务,并启用会话中间件,这一部分代码和前面一模一样。

与以前的版本相比,较新的2.2模板已经简化,因此为了保持一致性,我从2.0应用程序复制了HomeController。 我还复制了Index.chtml,About.chtml和Contact.cshtml视图文件。 最后,我更新了Layout.cshtml,为标题中的About和Contact页面添加了链接。

这2个应用程序,除了使用的ASP.NET Core版本不一样,其他的部分基本都是一样的。然而这次运行的时候,当你浏览一些页面之后,首页只会显示你访问过首页,而不会显示你访问过其他页面。

65831-20190314072933108-1204361696.png

不要点击隐私政策的横幅 - 后面你将马上知道原因

现在如果你去查看一下你的Cookies, 你会发现加密会话ID.AspNetCore.Session不存在。

65831-20190314073107544-1446811479.png

一切都显然配置正确,并且会话本身似乎也在工作(因为可以在Index.cshtml中成功检索HomeController.Index中设置的值)。 但当页面重新加载,或者在导航之间跳转的时候,没有保存会话状态。

那么为什么会话状态在ASP.NET Core 2.0中正常工作, 在ASP.NET Core 2.1/2.2中反而没有正常工作了呢?

到底发生了什么?GDPR#

问题的原因,是因为ASP.NET Core 2.1版本之后,引入了一些新功能。为了帮助开发人员遵守2018年生效的GDPR规则,ASP.NET Core 2.1版本引入了一些扩展点,以及模板的更新。

针对这些新功能的官方文档写的都很详细,这里我只做简单总结:

  • 同意Cookie对话框 - 默认情况下,在用户点击同意对话框之前,ASP.NET Core不会将“非必要”的cookies写入响应中
  • Cookie可以被设置为必要或者非必要的 - 无论用户是否同意,必要的Cookies都会发送给浏览器,非必要的Cookies需要得到用户的同意
  • 会话Cookie被认为是非必要的 - 因此,在用户同意之前,无法跨导航或页面重新加载跟踪会话。
  • 临时数据(Temp Data)是非必要的 - ASP.NET Core 2.0以上版本中,临时数据提供器使用Cookie来存储数据项,所以在用户同意之前,临时数据功能是不可用的

所以问题是我们需要用户同意使用Cookie。 如果单击隐私横幅上的“Accept”,则ASP.NET Core可以编写会话cookie,并恢复预期的功能。

65831-20190314073043837-1651824059.png

如何在ASP.NET Core 2.1及以上版本中使用会话状态#

根据你正在构建的程序,你可以使用多种选项。哪一个最适合你取决于你的使用场景,但是请注意,这些功能是为了帮助开发人员遵守GDPR而添加的。

如果你不在欧洲国家,或者你认为GDPR对自己没有什么影响,最好请阅读一下https://andrewlock.net/session-state-gdpr-and-non-essential-cookies/ - GDPR可能依然适用于你

这里主要的可选项如下:

  1. 在用户同意Cookie之前,接受该会话状态可能不可用。
  2. 在用户同意Cookie之前,禁用需要会话状态的功能。
  3. 禁用Cookie同意功能
  4. 将会话Cookie标记为必要的

我将在下面详细介绍每个选项,请记住考虑你的选择可能会影响你是否遵守GDPR!

接受当前的行为#

“最简单”的选择就是接受现有的行为。 ASP.NET Core中的会话状态通常只应用于临时数据,因此你的应用程序需要能够处理会话状态不可用的情况。

这取决于你使用会话的目的,可能可以实现或可能不能实现,但这是使用现有模板的最简单方法,并且将你接触GDPR问题方面风险降到了最低。

禁用需要会话的功能#

第二种选择和第一种选择类似,应为你需要保持现有的行为。区别在于第一种选项会将会话简单的视为缓存,因此你始终需要假设会话值是可以读取和保存的。而第二种选项略有不同,因为你需要明确知道系统中哪些部分是需要会话状态的,并在用户同意Cookie之前,禁用它们。

例如, 你可以需要一个会话状态保存当前页面选择的主题。如果用户没有同意Cookie, 那么你只需要隐藏主题选择的功能。只要用户同意,再将它显示出来。

这感觉就像是针对选择一的改进,因为它主要改善了用户体验。如果你不考虑哪些功能是需要会话的,用户可能会产生一些疑惑。例如,如果你使用选项一,用户在切换主题的时候,程序永远不会记住它们的选择,这就很让人沮丧。

禁用Cookie同意功能#

如果你确定不需要Cookie同意功能,你也可以很容易的禁用它。 默认模板在Startup.ConfigureServices中显式启用了Cookie同意功能。

 
 
Copy
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddSession(); // added to enable session services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }

这里CheckConsentNeeded属性是一个标记,它用于检查是否应将非必要的cookie写入响应。 如果函数返回true(如上所述,模板中的默认值),则跳过非必要的cookie。 将此更改为false并且会话状态将起作用,而不需要用户明确同意cookie。

标记会话Cookie是必要的#

完全禁用cookie同意功能可能会对你的应用程序造成一定的负担。 如果是这种情况,你可以将会话cookie标记为必要。

services.AddSession的重载方法,允许你传入一个会话配置对象。你可以使用它设置会话的超时时间,以及自定义会话Cookie。为了将会话Cookie标记为必要的,我们需要显式配置IsEssential的值是true。

 
 
Copy
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddSession(opts => { opts.Cookie.IsEssential = true; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }

使用这种方法,虽然应用程序依然会显示Cookie同意横幅,并且在点击之前不会写入非必要的Cookie。 但会议状态将在用户同意Cookie之前立即生效,因为它被认为是必要的。

总结#

在这篇文章中,我描述了一个曾经多次被问过问题。开发人员发现他们的会话状态没有正确保存。 这通常是由于ASP.NET Core 2.1中引入的Cookie同意和非必要cookie的GDPR功能引起的。

我展示了一个问题的实例,以及它在2.0 app和2.2 app之间的区别。 我描述了会话状态如何依赖于默认情况下被认为是非必要的会话Cookie,因此在用户同意Cookie之前不会写入响应。

最后,我描述了处理这种行为的四种方法:

  • 什么也不做,接受它

  • 禁用依赖会话状态的功能,直到同意为止

  • 取消同意要求

  • 标记会话Cookie为必要的Cookie。

哪种选择最适合你将取决于你正在构建的应用程序,以及你对GDPR和类似法规的认识。

相关文章
|
11天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
3月前
|
开发框架 前端开发 JavaScript
盘点72个ASP.NET Core源码Net爱好者不容错过
盘点72个ASP.NET Core源码Net爱好者不容错过
68 0
|
3月前
|
开发框架 .NET
ASP.NET Core NET7 增加session的方法
ASP.NET Core NET7 增加session的方法
37 0
|
3月前
|
开发框架 JavaScript .NET
ASP.NET Core的超级大BUG
ASP.NET Core的超级大BUG
41 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
38 0
|
1月前
|
开发框架 人工智能 .NET
C#/.NET/.NET Core拾遗补漏合集(持续更新)
C#/.NET/.NET Core拾遗补漏合集(持续更新)
|
1月前
|
开发框架 中间件 .NET
C# .NET面试系列七:ASP.NET Core
## 第一部分:ASP.NET Core #### 1. 如何在 controller 中注入 service? 在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务: 1、创建服务 首先,确保你已经在应用程序中注册了服务。这通常在Startup.cs文件的ConfigureServices方法中完成。例如: ```c# services.AddScoped<IMyService, MyService>(); //
60 0
|
2月前
|
开发框架 前端开发 .NET
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
为了便于大家查找,特将之前开发的.Net Core相关的五大案例整理成文,共计440页,32w字,免费提供给大家,文章底部有PDF下载链接。
32 1
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
|
2月前
|
算法 BI API
C#/.NET/.NET Core优秀项目和框架2024年1月简报
C#/.NET/.NET Core优秀项目和框架2024年1月简报
|
3月前
|
算法 C#
C# .Net Core bytes转换为GB/MB/KB 算法
C# .Net Core bytes转换为GB/MB/KB 算法
34 0