在核心授权属性:"Cannot access a disposed object"中使用存储库 ASP.NET



对于第三方身份验证,我需要自定义Authorize属性。这里需要一个存储库(SessionManager)类来检查用户是否登录。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class VBAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter {
public async void OnAuthorization(AuthorizationFilterContext context) {
var sessionManager = (VBSessionManager)context.HttpContext.RequestServices.GetService(typeof(VBSessionManager));
var user = await sessionManager.GetCurrentSessionAsync();
if (user == null) {
context.Result = new UnauthorizedResult();
return;
}
}
}

在类似情况下sessionManager.GetCurrentSessionAsync()发生以下异常:

无法访问已释放的对象。此错误的常见原因是 释放从依赖关系注入中解析的上下文,以及 然后尝试在其他地方使用相同的上下文实例 应用。如果您在 上下文,或将上下文包装在 using 语句中。如果你是 使用依赖注入,你应该让依赖注入 容器负责释放上下文实例。对象名称: "异步处理器"。

我知道这一点,不会自己处理。VBSessionManager将我的DbContext注入到其构造函数中。在内部GetCurrentSessionAsynccookie是用LinQ数据库查询来检查的。所以没有召唤Disposeusing指令或类似的东西。

注射VBSessionManager

public class VBSessionManager {
readonly VBDbContext db;
readonly IHttpContextAccessor contextAccessor;
const string sessionHashCookieName = "xxx";
VBSession currentSession;
public VBSessionManager(VBDbContext db, IHttpContextAccessor contextAccessor) {
this.db = db;
this.contextAccessor = contextAccessor;
}
public async Task<VBSession> GetCurrentSessionAsync() {
if (currentSession == null) {
string sessionCookie = GetCookieWithoutPrefix(sessionHashCookieName);
currentSession = await GetSessionAsync(sessionCookie);
if (currentSession == null) {
var cookieUser = GetUserFromCookiePassword().Result;
// No session detected
if (cookieUser == null) {
return null;
}
currentSession = db.Sessions.FirstOrDefault(s => s.UserId == cookieUser.Id);
}
}
return currentSession;
}
// ...
}

注入服务

services.AddDbContext<VBDbContext>(options => {
string connectionString = Configuration.GetValue<string>("VBConnectionString");
options.UseMySql(connectionString,
mySqlOptions => {
mySqlOptions.ServerVersion(new Version(10, 2, 19), ServerType.MariaDb);
}
);
bool isDev = CurrentEnvironment.IsDevelopment();
options.EnableSensitiveDataLogging(isDev);
});
services.AddScoped<VBSessionManager>();

似乎使用async会导致问题。当我OnAuthorization更改为这样的同步方法时,我没有收到任何错误:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class VBAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationFilterContext context) {
var sessionManager = (VBSessionManager)context.HttpContext.RequestServices.GetService(typeof(VBSessionManager));
var user = sessionManager.GetCurrentSessionAsync().Result;
if (user == null) {
context.Result = new UnauthorizedResult();
return;
}
}
}

不知道这些属性(或者可能只有AuthorizeAttribute)是否不是为异步工作而设计的。对我来说,当前的解决方法是使用 syn 方法。我也认为这不应该降低性能。但是,如果有人知道背景,甚至知道如何使用属性异步,我会很高兴另一个answear。

public async void OnAuthorization(AuthorizationFilterContext context) {

这里重要的是使用async void,根据大卫·福勒的说法,这总是不好的。使用此处的设置,无法awaitOnAuthorization本身的调用,这意味着正在发生类似以下的事情:

  1. 在调用OnAuthorization方法之前,将创建VBSessionManagerVBDbContext的作用域实例一段时间。
  2. 您的OnAuthorization执行并调用VBSessionManager.GetCurrentSessionAsync,在所述方法有机会完成之前返回(由于使用了async/await)。
  3. 随着OnAuthorization的完成,IDisposable实施VBDbContext被处置。
  4. VBSessionManager.GetCurrentSessionAsync中的代码仍在运行 - 它尝试使用已释放的VBDbContext实例。

在您的情况下使用async void的原因仅仅是因为这是IAuthorizationFilter接口中声明的内容 - 您想使用await,唯一的方法是将您的实现方法标记为async(您无法将其async Task因为这不会实现接口)。

就解决方案而言,我同意Gabriel Luci的观点,即使用基于策略的授权将是要走的路。

public class VBAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public async void OnAuthorization(AuthorizationFilterContext context)
{
// …
await something;
// …
}
}

拥有async void的方法几乎总是一个坏主意。异步方法应返回Task,以使调用方能够确定异步进程的结果。

由于您正在实现IAuthorizationFilter,因此您正在实现同步授权过滤器。当您不需要异步执行某些操作时,可以使用此选项。例如,如果您只需要查看某些参数,然后有一些规则来确定是否允许访问,则为真。

如果需要异步进程,则不应使void方法异步,而应实现IAsyncAuthorizationFilter。这是用于实现异步授权筛选器的接口。在这种情况下,您需要实现的方法看起来有点不同:

Task OnAuthorizationAsync(AuthorizationFilterContext context)

如您所见,此方法返回一个Task,以便它可以正确执行异步进程。在您的情况下,如果您想在方法中await某些内容,您可以这样做:

public class VBAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// …
await something;
// …
}
}

现在,使用返回Task的正确异步方法,调用系统将能够正确使用该方法,并且请求处理的继续将等待您的授权过滤器被处理。

OnAuthorization方法不应用于验证授权。这只是一个通知,"嘿,授权现在正在发生"。

也就是说,有些人已经将其用于此。但是由于您将其声明为async void,因此没有任何东西等待此方法完成。这就是异常的根源:在进行数据库调用时,请求已经完成,上下文已释放。您可以删除async....

但正确的解决方案是 使用IAuthorizationHandler,顾名思义,它专为处理授权而设计。它有一个HandleAsync方法,这是一个实际等待的正确async方法(它等待您对授权的决定,然后再继续)。

看看一位Microsoft员工的回答。设置处理程序,然后将其与常规AuthorizeAttribute一起使用,如下所示:

[Authorize(Policy = "MyCustomPolicy")]

最新更新