在測試一個例子時發現的問題,這個示例實現的功能是掃清頁面也能保持表格鎖定列的狀態,先看下頁面的完成效果:
測試中發現,幾乎相同的程式碼:
- 在 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(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 中,會將鎖定列的狀態改變回發到後臺:
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.GetObjectstring>>(KEY_FOR_DATASOURCE_SESSION) == null) { HttpContext.Session.SetObject(KEY_FOR_DATASOURCE_SESSION, new List<string>() { }); } return HttpContext.Session.GetObjectstring>>(KEY_FOR_DATASOURCE_SESSION); }
上面是儲存狀態的邏輯,而掃清頁面後,會從Session中讀取儲存的列鎖定狀態:
// GET: GridLockColumn/SaveToDB public ActionResult Index() { LoadData(); return View(); } private void LoadData() { ViewBag.LockedColumns = GetLockedColumns(); }
然後,在頁面檢視中,將儲存的列鎖定狀態設定到表格上,如下所示:
@{ Grid grid1 = F.GetControl("Grid1"); List<string> lockedColumns = ViewBag.LockedColumns as List<string>; if (lockedColumns.Count > 0) { 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.GetObjectstring>>(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 { /// /// Session擴充套件 ///
public static class SessionExtension
{
///
/// 設定Session物件
///
///
///
///
///
public static void SetObject(this ISession session, string key, T obj)
{
session.SetString(key, JsonConvert.SerializeObject(obj));
}
///
/// 獲取Session物件
///
///
///
///
///
public static T GetObject(this ISession session, string key)
{
T result = default(T);
var value = session.GetString(key);
if(!String.IsNullOrEmpty(value))
{
result = JsonConvert.DeserializeObject(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(0, 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(); }
原文地址:https://www.cnblogs.com/sanshi/p/10550977.html