![](/img/trans.png)
[英]Cannot access a disposed object in ASP.NET Core when injecting 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的说法 ,这总是很糟糕的。 使用此处的设置,无法await
对OnAuthorization
本身的调用,这意味着正在发生类似以下的事情:
OnAuthorization
方法之前,需要一段时间创建VBSessionManager
和VBDbContext
作用域实例。 OnAuthorization
执行并调用VBSessionManager.GetCurrentSessionAsync
,在所述方法有机会完成之前返回(由于使用了async
/ await
)。 OnAuthorization
后,将部署VBDbContext
IDisposable
VBDbContext。 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感到满意。
OnAuthorization
将OnAuthorization
方法用于验证授权。 这只是一个通知,“嘿,授权正在进行中”。
就是说, 有人为此使用了它 。 但是,由于您将其声明为async void
,因此没有任何方法等待此方法完成。 那就是您例外的根源:在进行数据库调用时,请求已经完成,并且上下文已处置。 您可以只删除async
...。
但是正确的解决方案是使用IAuthorizationHandler
,其名称专门用于处理授权。 它有一个HandleAsync
方法,这是一个实际上正在等待的正确的async
方法(它等待您对授权的决定,然后继续)。
看看Microsoft员工提供的答案 。 设置处理程序,然后将其与常规的AuthorizeAttribute
一起使用,如下所示:
[Authorize(Policy = "MyCustomPolicy")]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.