歡迎光臨
每天分享高質量文章

為什麼我的會話狀態在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應用程式

  • 一個用戶在會話狀態中設置了一個字串值,例如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版本。

dotnet new globaljson --sdk-version 2.1.202

然後使用dotnet new命令創建一個新的ASP.NET Core MVC 2.0應用程式

dotnet new mvc --framework netcoreapp2.0

會話狀態預設情況下是沒有啟用的,所以這裡你需要先添加必要的服務。我們修改Startup.cs檔案ConfigureServices方法來添加會話服務。預設情況下,ASP.NET Core將使用記憶體來儲存會話信息,這對於測試來說很友好,但是生產環境中可能就需要替換為其他方式。

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

當然,只添加服務是沒有用的,我們還需要在管道中註冊會話中間件。只有註冊在會話中間件之後的中間件才可以訪問會話狀態,所以你需要將會話中間件放在MVC中間件之前。

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的代碼:

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”的會話值

@using Microsoft.AspNetCore.Http
@{
    ViewData["Title"] = "Home Page";
}

    @Context.Session.GetString("actions")

如果你現在運行應用程式並瀏覽幾次,你將看到會話頁面訪問歷史串列的構建。 在下麵的示例中,我訪問了主頁三次,關於頁面兩次:

如果查看當前頁面關聯的Cookie信息,你就會看到一個名為.AspNetCore.Session的Cookie, 它的值就是一個加密會話ID, 如果你刪除這個Cookie, 你將會看到”actions”的值被重置,頁面訪問歷史串列丟失。

這種會話狀態的行為就是大部分人所期望的,所以這裡沒有問題。但是當你使用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應用程式:

dotnet new mvc

這裡你依然需要顯示註冊會話服務,並啟用會話中間件,這一部分代碼和前面一模一樣。

與以前的版本相比,較新的2.2模板已經簡化,因此為了保持一致性,我從2.0應用程式複製了HomeController。 我還複製了Index.chtml,About.chtml和Contact.cshtml視圖檔案。 最後,我更新了Layout.cshtml,為標題中的About和Contact頁面添加了鏈接。

這2個應用程式,除了使用的ASP.NET Core版本不一樣,其他的部分基本都是一樣的。然而這次運行的時候,當你瀏覽一些頁面之後,首頁只會顯示你訪問過首頁,而不會顯示你訪問過其他頁面。

不要點擊隱私政策的橫幅 – 後面你將馬上知道原因

 

現在如果你去查看一下你的Cookies, 你會發現加密會話ID.AspNetCore.Session不存在。

一切都顯然配置正確,並且會話本身似乎也在工作(因為可以在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,並恢復預期的功能。

如何在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同意功能。

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。

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和類似法規的認識。

 

    赞(0)

    分享創造快樂