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.HasStarted
为true,调用Session.Set()
,就会抛出异常。
重要提示:如果不满足条件 1,则在响应开始后调用 Session.Set()
将不会导致错误。
这就是为什么错误似乎只在第一次加载页面时发生的原因——这是因为通常在第一次加载时,没有服务器可以使用的会话 cookie(或者提供的 cookie 无效或太old),并且服务器必须启动一个新的会话数据存储(我不知道为什么它必须为 Set() 启动一个新的会话数据存储,这就是为什么我说我认为这是一个错误)。如果服务器必须启动一个新的会话数据存储,它会在第一次调用 Session.Set()
时这样做,并且新的会话数据存储 不能 在响应完成后启动开始了。另一方面,如果提供的会话 cookie 是有效的,则不需要启动新的数据存储,因此您可以随时调用 Session.Set()
,包括响应开始后。
您需要做的是在响应开始之前对 Session.Set()
进行初步调用,以便会话数据存储启动,然后您对 Session.Set()
的调用将不会成功'不会导致错误。
我正在尝试在我的 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.HasStarted
为true,调用Session.Set()
,就会抛出异常。
重要提示:如果不满足条件 1,则在响应开始后调用 Session.Set()
将不会导致错误。
这就是为什么错误似乎只在第一次加载页面时发生的原因——这是因为通常在第一次加载时,没有服务器可以使用的会话 cookie(或者提供的 cookie 无效或太old),并且服务器必须启动一个新的会话数据存储(我不知道为什么它必须为 Set() 启动一个新的会话数据存储,这就是为什么我说我认为这是一个错误)。如果服务器必须启动一个新的会话数据存储,它会在第一次调用 Session.Set()
时这样做,并且新的会话数据存储 不能 在响应完成后启动开始了。另一方面,如果提供的会话 cookie 是有效的,则不需要启动新的数据存储,因此您可以随时调用 Session.Set()
,包括响应开始后。
您需要做的是在响应开始之前对 Session.Set()
进行初步调用,以便会话数据存储启动,然后您对 Session.Set()
的调用将不会成功'不会导致错误。