HttpContext.Session 在 Blazor 服务器应用程序中

HttpContext.Session in Blazor Server Application

我正在尝试在我的 ASP.NET Core Blazor Server 应用程序中使用 HttpContext.Session(如本 MS Doc 中所述,我的意思是:在启动时全部正确设置)

这是我尝试设置值时的代码部分:

var session = _contextAccessor.HttpContext?.Session;
if (session != null && session.IsAvailable)
{
    session.Set(key, data);
    await session.CommitAsync();
}

当此代码在 Razor 组件的 OnAfterRenderAsync 中调用时,session.Set 抛出以下异常:

The session cannot be established after the response has started.

我(可能)理解消息,但这使得会话基础结构非常不可用:应用程序需要在执行的每个阶段访问它的状态...

问题

我是否应该完全忘记 DistributedSession 基础设施,转而使用 Cookie 或 Browser SessionStorage? ...还是这里仍然使用 HttpContext.Session 的解决方法?我不想为了更低级别的实现而放弃分布式会话基础设施...

(仅作记录:浏览器的会话存储不跨选项卡,这很痛苦)

Blazor 从根本上与传统服务器端会话的概念不兼容,尤其是在没有服务器端的客户端或 WebAssembly 托管模型中。但是,即使在 "server-side" 托管模型中,与服务器的通信也是通过 websockets 进行的。只有一个初始请求。服务器端会话需要一个 cookie,该 cookie 必须在会话建立时发送给客户端,这意味着 only 您可以在第一次加载时做到这一点。之后,没有进一步的请求,因此没有机会建立会话。

docs 提供了有关如何在 Blazor 应用程序中维护状态的指导。对于最接近传统服务器端会话的东西,您正在考虑使用浏览器的 sessionStorage.

注意:我知道这个答案有点老,但我使用 WebSockets 的会话很好,我想分享我的发现。

回答

我认为您描述的这个 Session.Set() 错误是一个错误,因为 Session.Get() 即使在响应开始后也能正常工作,但 Session.Set() 则不然。无论如何,解决方法(或“hack”,如果你愿意的话)包括一次性调用 Session.Set() 以“启动”会话以供将来写入。只需在您的应用程序中找到您知道响应尚未发送的代码行,然后在其中插入对 Session.Set() 的一次性调用。然后,您将能够在 OnInitializedAsync() 方法内对 Session.Set() 进行后续调用而不会出现错误,包括响应开始后的调用。您可以通过检查 属性 HttpContext.Response.HasStarted.

来检查响应是否开始

尝试将此 app.Use() 片段添加到您的 Startup.cs Configure() 方法中。尽量确保该行位于 app.UseRouting():

之前的某处
...
...
app.UseHttpsRedirection();
app.UseStaticFiles();

//begin Set() hack
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
    //this throwaway session variable will "prime" the Set() method
    //to allow it to be called after the response has started
    var TempKey = Guid.NewGuid().ToString(); //create a random key
    Context.Session.Set(TempKey, Array.Empty<byte>()); //set the throwaway session variable
    Context.Session.Remove(TempKey); //remove the throwaway session variable
    await Next(); //continue on with the request
});
//end Set() hack

app.UseRouting();


app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});
...
...

背景信息

我可以在此处分享的信息不是 Blazor 特有的,但可以帮助您查明设置中发生的情况,因为我自己也遇到过同样的错误。当同时满足 BOTH 条件时,会发生错误:

条件 1. 向服务器发送的请求没有会话 cookie,或者包含的会话 cookie 是 invalid/expired。

条件 2。条件 1 中的请求在响应开始后调用 Session.Set()。也就是说,如果属性HttpContext.Response.HasStartedtrue,调用Session.Set(),就会抛出异常。

重要提示:如果不满足条件 1,则在响应开始后调用 Session.Set() 将不会导致错误。

这就是为什么错误似乎只在第一次加载页面时发生的原因——这是因为通常在第一次加载时,没有服务器可以使用的会话 cookie(或者提供的 cookie 无效或太old),并且服务器必须启动一个新的会话数据存储(我不知道为什么它必须为 Set() 启动一个新的会话数据存储,这就是为什么我说我认为这是一个错误)。如果服务器必须启动一个新的会话数据存储,它会在第一次调用 Session.Set() 时这样做,并且新的会话数据存储 不能 在响应完成后启动开始了。另一方面,如果提供的会话 cookie 是有效的,则不需要启动新的数据存储,因此您可以随时调用 Session.Set(),包括响应开始后。

您需要做的是在响应开始之前对 Session.Set() 进行初步调用,以便会话数据存储启动,然后您对 Session.Set() 的调用将不会成功'不会导致错误。