爲何個人會話狀態在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 Luhtml

在本篇博客中,我將描述一個關於會話狀態(Session State)的問題, 這個問題我已經被詢問了好幾回了。這個問題的場景以下:json

  • 建立一個新的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及以上版本。c#

說明:瀏覽器

  • 《通用數據保護條例》(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

  • 一個Cookie。 用來指定每一個用戶的惟一ID(Session ID)
  • 一個分佈式緩存。用來存儲與每一個用戶惟一ID關聯的數據項

在通常的狀況下,我會盡可能避免使用會話狀態,使用會話狀態可能會有不少陷阱,若是不注意,就會引發一塊兒沒必要要的問題。例如:session

  • 會話是針對每一個瀏覽器的,而不是每一個登陸用戶的
  • 會話結束的時候,應該刪除會話Cookie,但可能不會
  • 若是會話中沒有任何值,它將會被刪除,並從新生成一個新的會話ID
  • 本文中即將描述的GDPR問題

這裏咱們講解了什麼是會話狀態,以及其工做的原理。在下一節中,我將建立一個小程序,這個小程序會使用會話狀態存儲你訪問過的頁面,而後在首頁上顯示該列表。mvc

在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";
}

<div>
    @Context.Session.GetString("actions")
</div>

若是你如今運行應用程序並瀏覽幾回,你將看到會話頁面訪問歷史列表的構建。 在下面的示例中,我訪問了主頁三次,關於頁面兩次:

若是查看當前頁面關聯的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和相似法規的認識。

相關文章
相關標籤/搜索