在 ASP.NET Core Authorize-Attribute 中使用带有 DbContext 的存储库:“无法访问已处置的对象”
Posted
技术标签:
【中文标题】在 ASP.NET Core Authorize-Attribute 中使用带有 DbContext 的存储库:“无法访问已处置的对象”【英文标题】:Use repository with DbContext in ASP.NET Core Authorize-Attribute: "Cannot access a disposed object" 【发布时间】:2019-05-20 03:18:06 【问题描述】:对于第三方身份验证,我需要一个自定义 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 语句中。如果你是 使用依赖注入,你应该让依赖注入 容器负责处理上下文实例。对象名称: 'AsyncDisposer'。
我知道这一点,不要自行处置。 VBSessionManager
将我的 DbContext
注入到它的构造函数中。内部GetCurrentSessionAsync
cookie 使用 LinQ 数据库查询进行检查。所以不要调用Dispose
、using
指令或类似的东西。
注入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>();
【问题讨论】:
尝试从Scoped
切换到Singleton
或Transient
。对于DI
,我参考了这个(docs.microsoft.com/en-us/aspnet/core/fundamentals/…)
我在问题中编辑了我的 DbContext 的注入。
@RyanWilson 尝试过Transient
同样的问题。单例似乎不是一个好的做法,因为 DbContext
类旨在缩短活动时间(参见例如 codereview.stackexchange.com/a/15818/169176)
你能显示你的代码GetCurrentSessionAsync()
吗? dbcontext 可能已经被释放了。
@wannadream 方法在我的问题中添加。
【参考方案1】:
似乎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 方法。我也认为这不应该降低性能。但是,如果有人知道背景,甚至知道我们如何使用属性 async,我会很高兴另一个答案。
【讨论】:
仅供参考:docs.microsoft.com/en-us/dotnet/csharp/language-reference/…。await
表达式不会阻塞它正在执行的线程。相反,它会导致编译器注册 async 方法的其余部分作为等待任务的延续。然后控制权返回给异步方法的调用者。当任务完成时,它调用它的继续,并且异步方法的执行从它停止的地方继续。因此,当此处调用延续时,dbcontext 可能已经被释放。【参考方案2】:
public async void OnAuthorization(AuthorizationFilterContext context)
这里重要的是使用async void
,即according to David Fowler,总是不好。通过您在此处的设置,对OnAuthorization
的调用本身不能是await
ed,这意味着正在发生以下情况:
VBSessionManager
和 VBDbContext
的作用域实例在调用您的 OnAuthorization
方法之前的一段时间内被创建。
您的OnAuthorization
执行并调用VBSessionManager.GetCurrentSessionAsync
,在所述方法有机会完成之前返回(由于使用async
/await
)。
OnAuthorization
完成后,IDisposable
-实现VBDbContext
被释放。
VBSessionManager.GetCurrentSessionAsync
中的代码仍在运行 - 它尝试使用已被处理掉的 VBDbContext
实例。
在您的情况下使用async void
的原因仅仅是因为这是在IAuthorizationFilter
接口中声明的内容-您想使用await
,唯一的方法是将您的实现方法标记为@ 987654344@(你不能让它async Task
,因为那不会实现接口)。
就解决方案而言,我同意 Gabriel Luci 的观点,即使用 policy-based authorisation 将是可行的方法。
【讨论】:
请注意,这里还有一个IAsyncAuthorizationFilter
,应该使用它来允许过滤器正确异步。【参考方案3】:
public class VBAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
public async void OnAuthorization(AuthorizationFilterContext context)
// …
await something;
// …
有一个方法async void
是almost always a bad idea。异步方法应该返回一个Task
以使调用者能够确定异步过程的结果。
由于您正在实施IAuthorizationFilter
,因此您正在实施同步授权过滤器。当您不需要异步执行某些操作时,您可以使用它。例如,如果您只需要查看一些参数,然后通过一些规则来确定是否允许访问,则这是正确的。
如果您需要异步进程,您应该不使 void
方法异步,而是实现 IAsyncAuthorizationFilter
。这是实现异步授权过滤器的接口。在这种情况下,您需要实现的方法看起来有点不同:
Task OnAuthorizationAsync(AuthorizationFilterContext context)
如您所见,此方法返回Task
,因此它可以正确执行异步进程。在您的情况下,您想在方法内部 await
某些东西,您可以这样做:
public class VBAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
// …
await something;
// …
现在,使用返回 Task
的适当异步方法,调用系统将能够正确使用该方法,并且请求处理的继续将等待您的授权过滤器被处理。
【讨论】:
【参考方案4】:OnAuthorization
方法不应该用于验证授权。这只是“嘿,正在授权”的通知。
也就是说,some have used it for this。但是由于您将其声明为async void
,因此没有什么可以等待此方法完成。这就是您的异常的根源:在进行数据库调用时,请求已经完成并且上下文已被释放。您可以删除async
....
但正确的解决方案是使用IAuthorizationHandler
,顾名思义,它是为处理授权而设计的。它有一个HandleAsync
方法,这是一个正确的async
方法,实际上是在等待(它在继续之前等待您做出授权决定)。
查看来自 Microsoft 员工的 this answer。您设置处理程序,然后将其与常规 AuthorizeAttribute
一起使用,如下所示:
[Authorize(Policy = "MyCustomPolicy")]
【讨论】:
以上是关于在 ASP.NET Core Authorize-Attribute 中使用带有 DbContext 的存储库:“无法访问已处置的对象”的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET Core JWT 身份验证以保护 webAPI [Authorize] 属性错误 401 Unauthorized
ASP.NET Core 身份 [Authorize(Roles ="ADMIN")] 不起作用
拦截asp.net core Authorize action,授权成功后执行自定义动作
ASP.NET Core Authorize 属性不适用于 JWT