Session.SetString 方法在 ASP.NET Core 3.1 中抛出异常 "IFeatureCollection has been disposed. Object name: 'Collection'. "
Session.SetString method throws exception "IFeatureCollection has been disposed. Object name: 'Collection'. " in ASP.NET Core 3.1
我有一个用 ASP.NET Core 3.1 编写的项目。
我需要在Singleton服务中给Session设置数据:
_session.SetString("some key", "some value");
我从 DI 中注入了会话对象:
public OperatorService(ILogger<OperatorService> logger,
ISession session,
IOptions<AppSettings> options)
{
this._session = session;
this._logger = logger;
this._appSettings = options.Value;
}
我调用我的方法如下:
public void ChangeOperatorStatus(StatusChangeRequest request)
{
try
{
_session.SetString(request.Key, request.Value);
}
catch (Exception ex)
{
_logger.LogInformation($"Exception while changing status: {ex}");
}
}
但我得到以下异常:
IFeatureCollection has been disposed.\r\nObject name: 'Collection'.
并且我在 Startup.cs 的 ConfigureServices 方法中添加了一些代码:
services.AddHttpContextAccessor();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.HttpOnly = true;
})
.AddDistributedMemoryCache();
并且我在 Startup.cs 的 Configure 方法中添加了 app.UseSession();
。
我尝试了 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
并从 httpContextAccessor.HttpContext.Session
获得了会话,但我得到了同样的错误。
请帮帮我,谢谢。
HttpContext 是单个请求的上下文。它提供对该单个请求的请求、响应属性等的访问。您无法缓存它,一旦该请求结束,它就会失效。
会话是另一个短暂的东西——它只存在一个用户会话。网络应用程序的每个用户至少有一个会话。在单例中缓存其中一个会话可以保证
- 引用将在一段时间后失效,当会话过期并且
- 单例将只使用该用户的值,而忽略其他人的值。这本身就是一个错误,也是侵入应用程序的 绝妙 方法。
- 如果管理员登录,会话对象可能会在接下来的 20、30 或 60 分钟内将管理员的设置应用到每个人。
这就是为什么使用会话对每个请求中间件有意义,而不是单例服务。
HttpContext的正确用法
Session 只能通过请求的上下文到达,因此获得正确的会话意味着获得正确的 HttpContext。 David Fowler's ASP.NET Core Guidance 中解释了正确的方法:
❌ BAD This example stores the HttpContext in a field then attempts to use it later.
private readonly HttpContext _context;
public MyType(IHttpContextAccessor accessor)
{
_context = accessor.HttpContext;
}
public void CheckAdmin()
{
if (!_context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
✅ GOOD This example stores the IHttpContextAccesor itself in a field and uses the HttpContext field at the correct time (checking for null).
private readonly IHttpContextAccessor _accessor;
public MyType(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public void CheckAdmin()
{
var context = _accessor.HttpContext;
if (context != null && !context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
改为使用 Scoped 服务
因为 Singleton 不知道要使用哪个会话。一种选择是将该服务简单地转换为 Scoped 服务。在 ASP.NET Core 中,一个请求定义了一个范围。这就是控制器操作和管道中间件如何为每个请求访问正确的 HttpContext。
假设该服务被一个动作或中间件使用,也许唯一需要的改变是将 AddSingleton<ThatService>
替换为 AddScoped<ThatService>
扭转局面,或控制反转
另一个选项是 调用者 应该向它提供会话。而不是使用缓存会话,例如:
public void SetStatus(string status)
{
_session.SetString(SessionKeys.UserStatus, "some value");
}
请求会话或 HttpContext 作为参数:
public void SetStatus(string status,ISession session)
{
session.SetString(SessionKeys.UserStatus, "some value");
}
并让呼叫者将正确的会话传递给它
我花了一段时间才解决这个问题。
在我的例子中,它是一个 3.1 aspnetcore,直到我将容器函数从
public async void OnPost
至
public async Task<IActionResult> OnPost
看起来 HttpContext 在使用之前就被释放了...
我有一个用 ASP.NET Core 3.1 编写的项目。
我需要在Singleton服务中给Session设置数据:
_session.SetString("some key", "some value");
我从 DI 中注入了会话对象:
public OperatorService(ILogger<OperatorService> logger,
ISession session,
IOptions<AppSettings> options)
{
this._session = session;
this._logger = logger;
this._appSettings = options.Value;
}
我调用我的方法如下:
public void ChangeOperatorStatus(StatusChangeRequest request)
{
try
{
_session.SetString(request.Key, request.Value);
}
catch (Exception ex)
{
_logger.LogInformation($"Exception while changing status: {ex}");
}
}
但我得到以下异常:
IFeatureCollection has been disposed.\r\nObject name: 'Collection'.
并且我在 Startup.cs 的 ConfigureServices 方法中添加了一些代码:
services.AddHttpContextAccessor();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.HttpOnly = true;
})
.AddDistributedMemoryCache();
并且我在 Startup.cs 的 Configure 方法中添加了 app.UseSession();
。
我尝试了 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
并从 httpContextAccessor.HttpContext.Session
获得了会话,但我得到了同样的错误。
请帮帮我,谢谢。
HttpContext 是单个请求的上下文。它提供对该单个请求的请求、响应属性等的访问。您无法缓存它,一旦该请求结束,它就会失效。
会话是另一个短暂的东西——它只存在一个用户会话。网络应用程序的每个用户至少有一个会话。在单例中缓存其中一个会话可以保证
- 引用将在一段时间后失效,当会话过期并且
- 单例将只使用该用户的值,而忽略其他人的值。这本身就是一个错误,也是侵入应用程序的 绝妙 方法。
- 如果管理员登录,会话对象可能会在接下来的 20、30 或 60 分钟内将管理员的设置应用到每个人。
这就是为什么使用会话对每个请求中间件有意义,而不是单例服务。
HttpContext的正确用法
Session 只能通过请求的上下文到达,因此获得正确的会话意味着获得正确的 HttpContext。 David Fowler's ASP.NET Core Guidance 中解释了正确的方法:
❌ BAD This example stores the HttpContext in a field then attempts to use it later.
private readonly HttpContext _context;
public MyType(IHttpContextAccessor accessor)
{
_context = accessor.HttpContext;
}
public void CheckAdmin()
{
if (!_context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
✅ GOOD This example stores the IHttpContextAccesor itself in a field and uses the HttpContext field at the correct time (checking for null).
private readonly IHttpContextAccessor _accessor;
public MyType(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public void CheckAdmin()
{
var context = _accessor.HttpContext;
if (context != null && !context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
改为使用 Scoped 服务
因为 Singleton 不知道要使用哪个会话。一种选择是将该服务简单地转换为 Scoped 服务。在 ASP.NET Core 中,一个请求定义了一个范围。这就是控制器操作和管道中间件如何为每个请求访问正确的 HttpContext。
假设该服务被一个动作或中间件使用,也许唯一需要的改变是将 AddSingleton<ThatService>
替换为 AddScoped<ThatService>
扭转局面,或控制反转
另一个选项是 调用者 应该向它提供会话。而不是使用缓存会话,例如:
public void SetStatus(string status)
{
_session.SetString(SessionKeys.UserStatus, "some value");
}
请求会话或 HttpContext 作为参数:
public void SetStatus(string status,ISession session)
{
session.SetString(SessionKeys.UserStatus, "some value");
}
并让呼叫者将正确的会话传递给它
我花了一段时间才解决这个问题。 在我的例子中,它是一个 3.1 aspnetcore,直到我将容器函数从
public async void OnPost
至
public async Task<IActionResult> OnPost
看起来 HttpContext 在使用之前就被释放了...