繁体   English   中英

在ASP.NET Core授权属性中将存储库与DbContext一起使用:“无法访问已处置的对象”

[英]Use repository with DbContext in ASP.NET Core Authorize-Attribute: “Cannot access a disposed object”

对于第三方身份验证,我需要一个自定义的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() ,会发生以下异常:

无法访问已处置的对象。 导致此错误的常见原因是,处理从依赖项注入中解决的上下文,然后稍后尝试在应用程序中的其他位置使用相同的上下文实例。 如果在上下文上调用Dispose()或将上下文包装在using语句中,则可能会发生这种情况。 如果使用依赖项注入,则应让依赖项注入容器负责处理上下文实例。 对象名称:“ AsyncDisposer”。

我知道这一点,请勿自行处置。 VBSessionManager在其构造函数中注入了我的DbContext 在内部,使用LinQ数据库查询检查了GetCurrentSessionAsync Cookie。 因此, Dispose using指令或类似using调用Dispose

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>();
 public async void OnAuthorization(AuthorizationFilterContext context) { 

在这里重要的是使用async void根据David Fowler的说法 ,这总是很糟糕的。 使用此处的设置,无法awaitOnAuthorization本身的调用,这意味着正在发生类似以下的事情:

  1. 在调用OnAuthorization方法之前,需要一段时间创建VBSessionManagerVBDbContext作用域实例。
  2. 您的OnAuthorization执行并调用VBSessionManager.GetCurrentSessionAsync ,在所述方法有机会完成之前返回(由于使用了async / await )。
  3. 完成OnAuthorization后,将部署VBDbContext 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 ,因此要实现同步授权过滤器。 当您不需要异步执行某些操作时,可以使用它。 例如,如果您只需要查看一些参数,然后做出一些决定来确定是否允许访问,则为true。

如果您需要异步进程,你应该做的void方法异步而是实现IAsyncAuthorizationFilter 这是用于实现异步授权过滤器的接口。 在这种情况下,您需要实现的方法看起来有些不同:

Task OnAuthorizationAsync(AuthorizationFilterContext context)

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

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

        await something;

        // …
    }
}

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

似乎使用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感到满意。

OnAuthorizationOnAuthorization方法用于验证授权。 这只是一个通知,“嘿,授权正在进行中”。

就是说, 有人为此使用了它 但是,由于您将其声明为async void ,因此没有任何方法等待此方法完成。 那就是您例外的根源:在进行数据库调用时,请求已经完成,并且上下文已处置。 可以只删除async ...。

但是正确的解决方案是使用IAuthorizationHandler ,其名称专门用于处理授权。 它有一个HandleAsync方法,这是一个实际上正在等待的正确的async方法(它等待您对授权的决定,然后继续)。

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

[Authorize(Policy = "MyCustomPolicy")]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM