你注意到 .Net Framework 和 .Net Core 中使用 Session 的区别了吗?

摘要:
起因在测试一个例子时发现的问题,这个示例实现的功能是刷新页面也能保持表格锁定列的状态,先看下页面的完成效果:测试中发现,几乎相同的代码:在FineUIMvc(NetFramework)下没有问题:http://mvc.fineui.com/#/GridLockColumn/SaveToDB但是在FineUICore(NetCore)下就失效了,刷新页面后锁定列状态丢失:http://core.fi
起因

在测试一个例子时发现的问题,这个示例实现的功能是刷新页面也能保持表格锁定列的状态,先看下页面的完成效果:

你注意到 .Net Framework 和 .Net Core 中使用 Session 的区别了吗?第1张

测试中发现,几乎相同的代码:

这个例子使用了 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(100).EnableLock(true).Locked(true),
        F.RenderField().HeaderText("性别").DataField("Gender").FieldType(FieldType.Int).RendererFunction("renderGender").Width(80).EnableLock(true),
        F.RenderField().HeaderText("入学年份").DataField("EntranceYear").FieldType(FieldType.Int).Width(100).EnableLock(true),
        F.RenderCheckField().HeaderText("是否在校").DataField("AtSchool").RenderAsStaticField(true).Width(100).EnableLock(true),
        F.RenderField().HeaderText("所学专业").DataField("Major").RendererFunction("renderMajor").Width(300).EnableLock(true),
        F.RenderField().HeaderText("分组").DataField("Group").RendererFunction("renderGroup").Width(80).EnableLock(true),
        F.RenderField().HeaderText("注册日期").DataField("LogTime").FieldType(FieldType.Date).Renderer(Renderer.Date).RendererArgument("yyyy-MM-dd").Width(100).EnableLock(true)
    ).Listener("columnlock", "onGridColumnLock").Listener("columnunlock", "onGridColumnUnlock")
    .DataSource(DataSourceUtil.GetDataTable())
)

在客户端事件 columnlock 和 columnunlock 中,会将锁定列的状态改变回发到后台:

functiononGridColumnLock(event, columnId) {
    //触发后台事件
    F.doPostBack('@Url.Action("Grid1_ColumnLockUnlock")', {
        type: 'lock',
        columnId: columnId
    });
}
functiononGridColumnUnlock(event, columnId) {
    //触发后台事件
    F.doPostBack('@Url.Action("Grid1_ColumnLockUnlock")', {
        type: 'unlock',
        columnId: columnId
    });
}

后台会将列状态信息保存到 Session 中(实际项目中是要保存到数据库中的):

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Grid1_ColumnLockUnlock(string type, stringcolumnId)
{
    //模拟操作数据库中的数据
    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);
        }
    }
    returnUIHelper.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, stringcolumnId)
{
    //模拟操作数据库中的数据
    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);
        }
    }
    returnUIHelper.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
publicActionResult Index()
{
    LoadData();
    returnView();
}
private voidLoadData()
{
    ViewBag.LockedColumns =GetLockedColumns();
}

然后,在页面视图中,将保存的列锁定状态设置到表格上,如下所示:

@{
    Grid grid1 = F.GetControl<Grid>("Grid1");
    List<string> lockedColumns = ViewBag.LockedColumns as List<string>;
    if (lockedColumns.Count > 0)
    {
        foreach (GridColumn column ingrid1.Columns)
        {
            RenderBaseField field = column asRenderBaseField;
            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本身并没有定义这个方法,我们来看一眼:

usingMicrosoft.AspNetCore.Http;
usingNewtonsoft.Json;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
namespaceFineUICore
{
    /// <summary>
    ///Session扩展
    /// </summary>
    public static classSessionExtension
    {
        /// <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, stringkey, 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, stringkey)
        {
            T result = default(T);
            var value =session.GetString(key);
            if(!String.IsNullOrEmpty(value))
            {
                result = JsonConvert.DeserializeObject<T>(value);
            }
            returnresult;
        }
    }
}

为什么 Session 中保存个对象还要通过JSON字符串中转?

原来 .Net Core 中原生只提供了在 Session 中保存字符串和 byte 数组的支持,想要保存复杂类型,只能自己写扩展方法了。

而这个扩展方法 GetObject 返回的Session对象的确像是一个深度拷贝的对象,因此对于它的 Add 和 Remove 并不会影响 Session 中实际存储的 JSON字符串。

至此,问题已经很明朗了,我们再来复习下 ASP.NET Core 中使用 Session 的步骤:

1. 首先在 Startup.cs 中添加 Session 服务

public voidConfigureServices(IServiceCollection services)
{
    services.AddDistributedMemoryCache();
    services.AddSession();
    //FineUI 和 MVC 服务
services.AddFineUI(Configuration);
    services.AddMvc(options =>
    {
        //自定义模型绑定(Newtonsoft.Json)
        options.ModelBinderProviders.Insert(0, newJsonModelBinderProvider());
    });
}
public voidConfigure(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 里面定义的扩展方法提供的:

你注意到 .Net Framework 和 .Net Core 中使用 Session 的区别了吗?第2张

解决

知道了根本原因,再去修正 FineUICore(ASP.NET Core)下的这个问题就简单多了。

在控制器方法中,修改完lockedColumns 对象后,需要显式的保存到 Session 中,如下所示:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Grid1_ColumnLockUnlock(string type, stringcolumnId)
{
    //模拟操作数据库中的数据
    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);
    returnUIHelper.Result();
}

喜欢三石和他的文章,就加入[三石和他的朋友们]知识星球,可以下载 FineUICore(基础版),下载后永久商用,可运行于Linux,macOS,Windows。

免责声明:文章转载自《你注意到 .Net Framework 和 .Net Core 中使用 Session 的区别了吗?》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇[POI2006]Tet-Tetris 3Dcss3动画:执行前不显示,执行后显示下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Cookie和Session

会话概述 什么是会话:用户打开一个浏览器访问页面,访问网站的很多页面,访问完成后将浏览器关闭的过程称为是一次会话。常见的会话技术:* Cookie:将数据保存到客户端浏览器。* Session:将数据保存到服务器端。 Cookie 技术的使用 向浏览器保存数据:HttpServletResponse 的方法:  * void addCookie(Cook...

php之 常用的 流程管理

1.流程管理的用法是什么样的? 2.怎么发起想要的流程? 3.审批的人要是怎么审批通过? 4.流程审核是不是要挨个走过? 一、要有数据库的内容的 肯定会有表的,首先就是用户表了,然后就是流程表,用户编写的流程表,还有审核人员的表           二、数据库结束后,就是新建流程的页面,这页面会有审核人员,还有流程的名字还有提交的按钮 (1)添加节点的人员...

spring boot jpa

spring boot jpa 访问数据库的方式一般来说有两种,一种以Java Entity为中心,将实体和实体关系对应到数据库的表和表关系,例如Hibernate框架(Spring Data JPA由此实现);另一种以原生SQL为中心,更加灵活便捷,例如Mybatis。这里重点介绍下Spring Data JPA技术。 spring boot jpa介绍...

在线HTML编辑器使用入门(Kindeditor)

 官网: http://kindeditor.net/demo.php   解压,开发中只需要导入选中的文件(通常在 webapp 下,建立 editor 文件夹 )    在使用 kindeditor 页面 导入   <!--导入在线HTML编辑器 --> <script type="text/javascript" sr...

ElementUI中el-upload传递额外参数为date类型时后台SpringBoot接收不到

场景 ElementUI中el-upload怎样上传文件并且传递额外参数给Springboot后台进行接收: https://mp.csdn.net/console/editor/html/107979828 上面讲了怎样使用el-upload控件传递给后台进行接收。 可以看到el-upload传递额外的参数时使用的data格式为 :data="{upda...

Hibernate的查询语言之HQL(一)——快速入门

  Hibernate提供异常强大的查询体系,使用Hibernat有多种查询方式可以选择:即可以使用Hibernate的HQL查询,也可以使用条件查询,甚至可以使用原生的SQL查询语句。不仅如此, Hibernate还提供了一种数据过滤功能,这些都用于筛选目标数据。   Hibernate是 Hibernate Query Language的缩写,HQL的...