简体   繁体   English

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

[英]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

Injection in 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;
    }
    // ...
}

Injection of services 服务注入

        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: 使用此处的设置,无法awaitOnAuthorization本身的调用,这意味着正在发生类似以下的事情:

  1. Scoped instances of VBSessionManager and VBDbContext are being created some amount of time before invoking your OnAuthorization method. 在调用OnAuthorization方法之前,需要一段时间创建VBSessionManagerVBDbContext作用域实例。
  2. Your 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 )。
  3. As OnAuthorization has completed, the IDisposable -implementing VBDbContext is disposed. 完成OnAuthorization后,将部署VBDbContext IDisposable VBDbContext。
  4. The code inside 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. OnAuthorizationOnAuthorization方法用于验证授权。 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.

相关问题 注入 DbContext 时无法访问 ASP.NET Core 中已处理的对象 - Cannot access a disposed object in ASP.NET Core when injecting DbContext C#:使用 IQueryable 注入 DbContext 时,无法访问 ASP.NET 核心中已处置的 object - C#: Cannot access a disposed object in ASP.NET Core when injecting DbContext with IQueryable 无法访问ASP.NET Core后台服务中的已处置对象 - Cannot access a disposed object in ASP.NET Core background service 无法访问已处置的 object Asp.net Identity Core - Cannot access a disposed object Asp.net Identity Core 无法在.NET Core for Async方法中访问DbContext的已处置对象 - Cannot access a disposed object for DbContext in .NET Core for Async method 如何解决asp.net core 2.1中无法访问已处置对象? - How to solve Cannot access a disposed object in asp.net core 2.1? 无法访问已处理的对象。此错误的原因 - 与依赖注入相关的 Asp.net Core 异常 - Cannot access a disposed object.cause of this error - Asp.net Core Exception related to Dependency Injection Asp.net Core:无法访问已处置的对象(不同的错误) - Asp.net Core: Can not access a disposed object (different errors) System.ObjectDisposedException:无法访问已处理的对象,ASP.NET Core 3.1 - System.ObjectDisposedException: Cannot access a disposed object, ASP.NET Core 3.1 DbContext 实例何时在 ASP.NET Core 5 中得到处置 - When does a DbContext instance get disposed in ASP.NET Core 5
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM