簡體   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