你注意到 .Net Framework 和 .Net Core 中使用 Session 的区别了吗?
起因
在测试一个例子时发现的问题,这个示例实现的功能是刷新页面也能保持表格锁定列的状态,先看下页面的完成效果:

测试中发现,几乎相同的代码:
- 在 FineUIMvc(Net Framework)下没有问题:http://mvc.fineui.com/#/GridLockColumn/SaveToDB
- 但是在 FineUICore(Net Core)下就失效了,刷新页面后锁定列状态丢失:http://core.fineui.com/#/GridLockColumn/SaveToDB
这个例子使用了 Session 来保存表格的锁定状态,先来看下页面视图的定义:
@(F.Grid().IsFluid(true).CssClass("blockpanel").Title("表格").ShowHeader(true).ShowBorder(true).ID("Grid1").DataIDField("Id").DataTextField("Name").AllowColumnLocking(true)
.Columns(
F.RowNumberField(),
F.RenderField().HeaderText("姓名").DataField("Name").Width().EnableLock(true).Locked(true),
F.RenderField().HeaderText("性别").DataField("Gender").FieldType(FieldType.Int).RendererFunction("renderGender").Width().EnableLock(true),
F.RenderField().HeaderText("入学年份").DataField("EntranceYear").FieldType(FieldType.Int).Width().EnableLock(true),
F.RenderCheckField().HeaderText("是否在校").DataField("AtSchool").RenderAsStaticField(true).Width().EnableLock(true),
F.RenderField().HeaderText("所学专业").DataField("Major").RendererFunction("renderMajor").Width().EnableLock(true),
F.RenderField().HeaderText("分组").DataField("Group").RendererFunction("renderGroup").Width().EnableLock(true),
F.RenderField().HeaderText("注册日期").DataField("LogTime").FieldType(FieldType.Date).Renderer(Renderer.Date).RendererArgument("yyyy-MM-dd").Width().EnableLock(true)
).Listener("columnlock", "onGridColumnLock").Listener("columnunlock", "onGridColumnUnlock")
.DataSource(DataSourceUtil.GetDataTable())
)
在客户端事件 columnlock 和 columnunlock 中,会将锁定列的状态改变回发到后台:
function onGridColumnLock(event, columnId) {
// 触发后台事件
F.doPostBack('@Url.Action("Grid1_ColumnLockUnlock")', {
type: 'lock',
columnId: columnId
});
}
function onGridColumnUnlock(event, columnId) {
// 触发后台事件
F.doPostBack('@Url.Action("Grid1_ColumnLockUnlock")', {
type: 'unlock',
columnId: columnId
});
}
后台会将列状态信息保存到 Session 中(实际项目中是要保存到数据库中的):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Grid1_ColumnLockUnlock(string type, string columnId)
{
// 模拟操作数据库中的数据
List<string> lockedColumns = GetLockedColumns();
if (type == "lock")
{
if (!lockedColumns.Contains(columnId))
{
lockedColumns.Add(columnId);
}
}
else if (type == "unlock")
{
if (lockedColumns.Contains(columnId))
{
lockedColumns.Remove(columnId);
}
} return UIHelper.Result();
} private static readonly string KEY_FOR_DATASOURCE_SESSION = "GridLockColumn.SaveToDB"; // 模拟在服务器端保存数据
// 特别注意:在真实的开发环境中,不要在Session放置大量数据,否则会严重影响服务器性能
private List<string> GetLockedColumns()
{
if (Session[KEY_FOR_DATASOURCE_SESSION] == null)
{
Session[KEY_FOR_DATASOURCE_SESSION] = new List<string>() { };
}
return (List<string>)Session[KEY_FOR_DATASOURCE_SESSION];
}
当然,上面对 Session 的操作是在 FineUIMvc(ASP.NET MVC) 中的代码,也就是运行在 .Net Framework 下的代码。
FineUICore(ASP.NET Core)中的代码稍微不同,如下所示:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Grid1_ColumnLockUnlock(string type, string columnId)
{
// 模拟操作数据库中的数据
List<string> lockedColumns = GetLockedColumns();
if (type == "lock")
{
if (!lockedColumns.Contains(columnId))
{
lockedColumns.Add(columnId);
}
}
else if (type == "unlock")
{
if (lockedColumns.Contains(columnId))
{
lockedColumns.Remove(columnId);
}
} return UIHelper.Result();
} private static readonly string KEY_FOR_DATASOURCE_SESSION = "GridLockColumn.SaveToDB"; // 模拟在服务器端保存数据
// 特别注意:在真实的开发环境中,不要在Session放置大量数据,否则会严重影响服务器性能
private List<string> GetLockedColumns()
{
if (HttpContext.Session.GetObject<List<string>>(KEY_FOR_DATASOURCE_SESSION) == null)
{
HttpContext.Session.SetObject(KEY_FOR_DATASOURCE_SESSION, new List<string>() { });
}
return HttpContext.Session.GetObject<List<string>>(KEY_FOR_DATASOURCE_SESSION);
}
上面是保存状态的逻辑,而刷新页面后,会从Session中读取保存的列锁定状态:
// GET: GridLockColumn/SaveToDB
public ActionResult Index()
{
LoadData(); return View();
} private void LoadData()
{
ViewBag.LockedColumns = GetLockedColumns();
}
然后,在页面视图中,将保存的列锁定状态设置到表格上,如下所示:
@{
Grid grid1 = F.GetControl<Grid>("Grid1");
List<string> lockedColumns = ViewBag.LockedColumns as List<string>;
if (lockedColumns.Count > )
{
foreach (GridColumn column in grid1.Columns)
{
RenderBaseField field = column as RenderBaseField;
if (field == null)
{
continue;
}
if (lockedColumns.Contains(field.ColumnID) || lockedColumns.Contains(field.DataField))
{
field.Locked = true;
}
}
}
}
至此,整个流程全部完成。问题是,几乎一模一样的代码,为什么在 .Net Framework 下一切正常,而 .Net Core 下却出问题了?
溯源
经过代码调试,我们发现,在 .Net Core 下将状态保存到 Session 中后,再去 Session 中检查却不存在!
后来才发现,我们过于相信引用类型了,请看如下代码:
// 模拟操作数据库中的数据
List<string> lockedColumns = GetLockedColumns();
if (type == "lock")
{
if (!lockedColumns.Contains(columnId))
{
lockedColumns.Add(columnId);
}
}
else if (type == "unlock")
{
if (lockedColumns.Contains(columnId))
{
lockedColumns.Remove(columnId);
}
}
有过面向对象编程经验的同学都知道,lockedColumns实际上是Session中的一个对象引用,因此下面对此对象的 Add 和 Remove 操作会直接改变 Session 中的对象。
为什么 .Net Core 下,这个逻辑就失效了?
我第一个想到的是深拷贝,莫非下面的代码返回了一个 Session 对象的深拷贝?
HttpContext.Session.GetObject<List<string>>(KEY_FOR_DATASOURCE_SESSION)
转到 GetObject 方法的定义,我却发现自己的忘性有多大,却原来 GetObject 是自己很久之前定义的一个扩展方法,.Net Core本身并没有定义这个方法,我们来看一眼:
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text; namespace FineUICore
{
/// <summary>
/// Session扩展
/// </summary>
public static class SessionExtension
{
/// <summary>
/// 设置Session对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="session"></param>
/// <param name="key"></param>
/// <param name="obj"></param>
public static void SetObject<T>(this ISession session, string key, T obj)
{
session.SetString(key, JsonConvert.SerializeObject(obj));
} /// <summary>
/// 获取Session对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="session"></param>
/// <param name="key"></param>
/// <returns></returns>
public static T GetObject<T>(this ISession session, string key)
{
T result = default(T);
var value = session.GetString(key);
if(!String.IsNullOrEmpty(value))
{
result = JsonConvert.DeserializeObject<T>(value);
}
return result;
} }
}
为什么 Session 中保存个对象还要通过JSON字符串中转?
原来 .Net Core 中原生只提供了在 Session 中保存字符串和 byte 数组的支持,想要保存复杂类型,只能自己写扩展方法了。
而这个扩展方法 GetObject 返回的Session对象的确像是一个深度拷贝的对象,因此对于它的 Add 和 Remove 并不会影响 Session 中实际存储的 JSON字符串。
至此,问题已经很明朗了,我们再来复习下 ASP.NET Core 中使用 Session 的步骤:
1. 首先在 Startup.cs 中添加 Session 服务
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession(); // FineUI 和 MVC 服务
services.AddFineUI(Configuration);
services.AddMvc(options =>
{
// 自定义模型绑定(Newtonsoft.Json)
options.ModelBinderProviders.Insert(, new JsonModelBinderProvider());
}); } public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseSession(); // FineUI 和 MVC 中间件(确保 UseFineUI 位于 UseMvc 的前面)
app.UseFineUI();
app.UseMvc();
}
2. 控制器中使用 HttpContext.Session.SetString 来保存字符串
HttpContext.Session.SetString("StartedTime", "Started time:" + DateTime.Now.ToString());
var startedTime = HttpContext.Session.GetString("StartedTime");
如果我们看下 SetString 的定义,会知道甚至这个方法也是通过 Microsoft.AspNetCore.Http 里面定义的扩展方法提供的:

解决
知道了根本原因,再去修正 FineUICore(ASP.NET Core)下的这个问题就简单多了。
在控制器方法中,修改完 lockedColumns 对象后,需要显式的保存到 Session 中,如下所示:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Grid1_ColumnLockUnlock(string type, string columnId)
{
// 模拟操作数据库中的数据
List<string> lockedColumns = GetLockedColumns();
if (type == "lock")
{
if (!lockedColumns.Contains(columnId))
{
lockedColumns.Add(columnId);
}
}
else if (type == "unlock")
{
if (lockedColumns.Contains(columnId))
{
lockedColumns.Remove(columnId);
}
} HttpContext.Session.SetObject(KEY_FOR_DATASOURCE_SESSION, lockedColumns); return UIHelper.Result();
}
喜欢三石和他的文章,就加入[三石和他的朋友们]知识星球,可以下载 FineUICore(基础版),下载后永久商用,可运行于Linux,macOS,Windows。
你注意到 .Net Framework 和 .Net Core 中使用 Session 的区别了吗?的更多相关文章
- 在 ASP.NET CORE 中使用 SESSION
Session 是保存用户和 Web 应用的会话状态的一种方法,ASP.NET Core 提供了一个用于管理会话状态的中间件.在本文中我将会简单介绍一下 ASP.NET Core 中的 Session ...
- 在 ASP.NET CORE 中使用 SESSION (转载)
Session 是保存用户和 Web 应用的会话状态的一种方法,ASP.NET Core 提供了一个用于管理会话状态的中间件.在本文中我将会简单介绍一下 ASP.NET Core 中的 Session ...
- Asp.net Core中使用Session
前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Cor ...
- [转]Asp.net Core中使用Session
本文转自:http://www.cnblogs.com/sword-successful/p/6243841.html 前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. ...
- .net core中的Session以及HttpContext对象使用小结
session用于识别用户并保持用户信息,就是一个会话 ,在浏览器不关闭的前提下,可以保存用户的信息,比如登录的保存用户信息从一个网页跳转到另一个网页,你的用户信息就可以用session. .net ...
- 解析Asp.net Core中使用Session的方法
2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Core中引 ...
- .Net Core 中使用Session
1.在 Startup 中 ConfigureServices 添加Session public void ConfigureServices(IServiceCollection services) ...
- ASP.NET Core 中使用Session会话
添加Session Nuget包 更新Startup.cs文件 在ConfigureServices方法中添加如下代码 services.AddSession(options => { // S ...
- 在Asp.Net Core中使用Session
1.在Stratup.cs中配置Session public void ConfigureServices(IServiceCollection services) { services.AddSes ...
随机推荐
- 无废话--Mac OS, VS Code 搭建c/c++基本开发环境
无废话,直接上步骤. 1) 安装 xcode. 打开App Store,搜索xcode,进行下载安装. 2)执行命令: xcode-select --install 安装命令行工具. 3)安装VS C ...
- IM开发者的零基础通信技术入门(一):通信交换技术的百年发展史(上)
[来源申明]本文原文来自:微信公众号“鲜枣课堂”,官方网站:xzclass.com,原题为:<通信交换的百年沧桑(上)>,本文引用时已征得原作者同意.为了更好的内容呈现,即时通讯网在收录时 ...
- [Inside HotSpot] Visual Studio2017编译调试openjdk12
编译 下载并编译好freetype,然后安装cygwin并安装必要工具: autoconf make zip unzip 当前目录效果为: 然后cygwin打开,进入openjdk12目录输入命令进行 ...
- Spring Boot 2.2 增加了一个新功能,启动飞起~
前几天栈长分享了一个好玩的框架:一个比Spring Boot快44倍的Java框架!,是不是感觉 Spring Boot 略慢?今天讲一下 Spring Boot 添加的这个新特性,可以大大提升 Sp ...
- Jason Wang:记录自己所想所得(第一次作业)
这个作业属于哪个课程 <课程的链接> 这个作业要求在哪里 <作业要求的链接> 我在这个课程的目标是 学会现代软件工程的思想,运用到实际中去 这个作业在哪个具体方面帮助我实现目标 ...
- springboot打包不同环境配置与shell脚本部署
本篇和大家分享的是springboot打包并结合shell脚本命令部署,重点在分享一个shell程序启动工具,希望能便利工作: profiles指定不同环境的配置 maven-assembly-plu ...
- 补习系列(15)-springboot 分布式会话原理
目录 一.背景 二.SpringBoot 分布式会话 三.样例程序 四.原理进阶 A. 序列化 B. 会话代理 C. 数据老化 小结 一.背景 在 补习系列(3)-springboot 几种scope ...
- asp.net mvc 简单项目框架的搭建过程(一)对Bll层和Dal层进行充分解耦
学习asp.net 已经有近三个月的时间了,在asp.net mvc上花的时间最多,但个人真是有些菜,不得不说,asp.net mvc的水真的还是蛮深的.目前在公司实习,也见过公司几个项目的代码了.对 ...
- Jmeter API Performance Test
笔者最近了解 Apache组织开发了基于Java的压力测试工具Apache JMeter.如有兴趣可自行搜索它的相关信息.笔者记录了一些使用方法,如有错误或遗漏,欢迎联系改正. 官方下载地址:http ...
- Linux高级运维 第四章 文件的基本管理和XFS文件系统备份恢复
4.1 Linux系统目录结构和相对/绝对路径 4.1.1系统目录结构 在windows系统中,查看文件先进入相应的盘符,然后进入文件目录 在windows中,它是多根 c:\ d:\ e ...