[英]Use repository with DbContext in ASP.NET Core Authorize-Attribute: “Cannot access a disposed object”
For third party authentication, I need a custom Authorize
attribute. 对于第三方身份验证,我需要一个自定义的
Authorize
属性。 Here a repository ( SessionManager
) class is required to check if the user is logged in. 在这里,需要一个存储库(
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;
}
}
}
In the like sessionManager.GetCurrentSessionAsync()
the following exception occur: 在类似
sessionManager.GetCurrentSessionAsync()
,会发生以下异常:
Cannot access a disposed object.
无法访问已处置的对象。 A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application.
导致此错误的常见原因是,处理从依赖项注入中解决的上下文,然后稍后尝试在应用程序中的其他位置使用相同的上下文实例。 This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement.
如果在上下文上调用Dispose()或将上下文包装在using语句中,则可能会发生这种情况。 If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
如果使用依赖项注入,则应让依赖项注入容器负责处理上下文实例。 Object name: 'AsyncDisposer'.
对象名称:“ AsyncDisposer”。
I'm aware of this and don't to any disposing on my own. 我知道这一点,请勿自行处置。
VBSessionManager
got my DbContext
injected in its constructor. VBSessionManager
在其构造函数中注入了我的DbContext
。 Inside GetCurrentSessionAsync
cookies were checked with LinQ database queries. 在内部,使用LinQ数据库查询检查了
GetCurrentSessionAsync
Cookie。 So no calling of Dispose
, using
directives or something like that. 因此,
Dispose
using
指令或类似using
调用Dispose
。
VBSessionManager
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) {
Of importance here is the use of async void
, which is, according to David Fowler , ALWAYS bad. 在这里重要的是使用
async void
, 根据David Fowler的说法 ,这总是很糟糕的。 With the setup you have here, the call to OnAuthorization
itself cannot be await
ed, which means that something like the following is happening: 使用此处的设置,无法
await
对OnAuthorization
本身的调用,这意味着正在发生类似以下的事情:
VBSessionManager
and VBDbContext
are being created some amount of time before invoking your OnAuthorization
method. OnAuthorization
方法之前,需要一段时间创建VBSessionManager
和VBDbContext
作用域实例。 OnAuthorization
executes and makes a call to VBSessionManager.GetCurrentSessionAsync
, returning before said method has a chance to complete (due to the use of async
/ await
). OnAuthorization
执行并调用VBSessionManager.GetCurrentSessionAsync
,在所述方法有机会完成之前返回(由于使用了async
/ await
)。 OnAuthorization
has completed, the IDisposable
-implementing VBDbContext
is disposed. OnAuthorization
后,将部署VBDbContext
IDisposable
VBDbContext。 VBSessionManager.GetCurrentSessionAsync
is still running - it attempts to use the instance of VBDbContext
that has been disposed of. VBSessionManager.GetCurrentSessionAsync
内部的代码仍在运行-它尝试使用已处置的VBDbContext
实例。 The reason async void
is being used in your situation is simply because that's what is declared in the IAuthorizationFilter
interface - you want to use await
and the only way to do that is to mark your implementation method as async
(you can't make it async Task
because that wouldn't implement the interface). 在您的情况下使用
async void
的原因仅仅是因为这是在IAuthorizationFilter
接口中声明的-您要使用await
,而这样做的唯一方法是将实现方法标记为async
(您不能使其成为async Task
因为那将无法实现该接口)。
In terms of a solution to this, I'd agree with Gabriel Luci that using policy-based authorisation would be the way to go. 关于此问题的解决方案,我同意加布里埃尔·卢西(Gabriel Luci)的观点,那就是使用基于策略的授权 。
public class VBAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public async void OnAuthorization(AuthorizationFilterContext context)
{
// …
await something;
// …
}
}
Having a method async void
is almost always a bad idea . 使方法
async void
几乎总是一个坏主意 。 Asynchronous methods should return a Task
to make callers able to determine the result of the asynchronous process. 异步方法应返回
Task
以使调用者能够确定异步过程的结果。
Since you are implementing IAuthorizationFilter
, you are implementing a synchronous authorization filter. 由于要实现
IAuthorizationFilter
,因此要实现同步授权过滤器。 You use this when you do not need to do something asynchronously. 当您不需要异步执行某些操作时,可以使用它。 This is for example true if you just need to look at some of the parameters and then have some ruling to determine whether access is allowed or not.
例如,如果您只需要查看一些参数,然后做出一些决定来确定是否允许访问,则为true。
If you require asynchronous processes, you should not make the void
method asynchronous but instead implement IAsyncAuthorizationFilter
. 如果您需要异步进程,你不应该做的
void
方法异步而是实现IAsyncAuthorizationFilter
。 This is the interface for implementing an asynchronous authorization filter. 这是用于实现异步授权过滤器的接口。 In that case, the method you need to implement looks a bit different:
在这种情况下,您需要实现的方法看起来有些不同:
Task OnAuthorizationAsync(AuthorizationFilterContext context)
As you can see, this method returns a Task
so it can properly do asynchronous processes. 如您所见,此方法返回一个
Task
以便它可以正确执行异步进程。 In your case, where you want to await
something inside of the method, you can just do it: 在您的情况下,您想
await
方法中的某些内容,则可以这样做:
public class VBAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// …
await something;
// …
}
}
Now, with a proper asynchronous method that returns a Task
, the calling system will be able to consume the method properly and the continuation of the request handling will wait for your authorization filter to be processed. 现在,有了返回
Task
的适当异步方法,调用系统将能够正确使用该方法,并且继续进行请求处理将等待您的授权过滤器得到处理。
It seems that the usage of async
cause problems. 似乎使用
async
会导致问题。 When I change OnAuthorization
to a sync method like this, I don't get any errors: 当我将
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;
}
}
}
Don't know if those attributes (or maybe only the AuthorizeAttribute
) isn't designed to work async. 不知道这些属性(或仅是
AuthorizeAttribute
)是否不是设计为异步工作的。 For me the current workaround is to use syn method. 对我来说,当前的解决方法是使用syn方法。 I also think that this shouldn't decrease performance.
我也认为这不会降低性能。 But if someone know about the backgrounds and even have a idea how we can use the attribute async, I'd be happy about another answear.
但是,如果有人知道背景,甚至知道如何使用异步属性,那么我会对另一个answear感到满意。
The OnAuthorization
method is not supposed to be used for verifying authorization. OnAuthorization
将OnAuthorization
方法用于验证授权。 It's just a notification that "hey, authorization is happening now". 这只是一个通知,“嘿,授权正在进行中”。
That said, some have used it for this . 就是说, 有人为此使用了它 。 But since you declared it as
async void
, nothing is waiting for this method to finish. 但是,由于您将其声明为
async void
,因此没有任何方法等待此方法完成。 That's the root of your exception: by the time the database call is made the request is already finished and the context is disposed. 那就是您例外的根源:在进行数据库调用时,请求已经完成,并且上下文已处置。 You can just remove the
async
.... 您可以只删除
async
...。
But the proper solution is use IAuthorizationHandler
, which is designed for, like the name implies, handling authorization. 但是正确的解决方案是使用
IAuthorizationHandler
,其名称专门用于处理授权。 It has a HandleAsync
method, which is a proper async
method that is actually awaited (it waits for your decision on authorization before continuing). 它有一个
HandleAsync
方法,这是一个实际上正在等待的正确的async
方法(它等待您对授权的决定,然后继续)。
Take a look at this answer from a Microsoft employee. 看看Microsoft员工提供的答案 。 You setup the handler, then use it with the regular
AuthorizeAttribute
like this: 设置处理程序,然后将其与常规的
AuthorizeAttribute
一起使用,如下所示:
[Authorize(Policy = "MyCustomPolicy")]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.