【问题标题】:ObjectDisposedException in .NET Core middleware with Entity Framework带有实体框架的 .NET Core 中间件中的 ObjectDisposedException
【发布时间】:2018-05-17 04:09:32
【问题描述】:

我有一个用 .NET Core 编写的 API。我有一个令牌中间件,但我遇到了一些问题。

如果我一次从一个客户端向中间件发送请求,那很好。如果我尝试一次由多个客户端使用中间件,则会出现错误。

据我所知,我认为_userManager 不会在每次发出请求时都创建一个新实例,我认为这会导致我的问题。我似乎要么得到

System.InvalidOperationException:在配置时尝试使用上下文

System.ObjectDisposedException:无法访问已处置的对象

我在想我注入的 UserManager<ApplicationUser> 可能是一个单例?也许两个请求都试图使用_userManager 的同一个实例,第一个处理它而第二个请求错误?

我对此有点陌生,所以也许我离基地很远。也许_userManager 不应该以这种方式使用,或者我注入它不正确。

这是来自 starup.cs 和令牌中间件的一些代码。下面是一些错误日志。

startup.cs 的一部分

public void ConfigureServices(IServiceCollection services)
{
     ...
     services.AddDbContext<ApplicationDbContext>(options =>
     {                                                                    


options.UseNpgsql(Environment.GetEnvironmentVariable("DbConnString"));                        
     });            


        services.AddIdentity<ApplicationUser, IdentityRole>(o=>
        {
            o.Password.RequireDigit = false;
            o.Password.RequiredLength = 0;
            o.Password.RequireLowercase = false;
            o.Password.RequireNonAlphanumeric = false;
            o.Password.RequireUppercase = false;
            o.SignIn.RequireConfirmedEmail = true;
            o.User.RequireUniqueEmail = true;

        })

                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
        services.AddAuthentication(o =>
        {
            o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

        }).AddJwtBearer(options =>
        {
            options.TokenValidationParameters = tokenValidationParameters;
        });
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
     ...
      app.UseJWTTokenProviderMiddleware(Options.Create(jwtOptions));            

        app.UseAuthentication();
        app.UseMvc();
     ...
 }

TokenProviderMiddleware

public class TokenProviderMiddleware
{           
    private readonly RequestDelegate _next;
    private TokenProviderOptions _options;
    private UserManager<ApplicationUser> _userManager;
    private readonly ILogger<TokenProviderMiddleware> _logger;
    public TokenProviderMiddleware(RequestDelegate next, IOptions<TokenProviderOptions> options, ILogger<TokenProviderMiddleware> logger)
    {
        _next = next;
        _options = options.Value;
        _logger = logger;            
    }

    public async Task Invoke(HttpContext context, UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;


        if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
        {
            await _next(context);
            return;
        }
        if (!context.Request.Method.Equals("POST") || !context.Request.HasFormContentType)
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("Bad Request");
            return;
        }

        ApplicationUser user = null;
        string refreshToken;
        string accessToken;

        if (context.Request.Form["grant_type"].Equals("refresh_token"))
        {
            refreshToken = context.Request.Form["refresh_token"];
            if(String.IsNullOrEmpty(refreshToken))
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsync("refresh_token not sent");   
                return;
            }


            // Check that the refresh_token is valid
            // I don't like using exceptions for flow control but it's how ValidateToken works
            try
            {
                ValidateRefreshToken(refreshToken);
            }
            catch(SecurityTokenInvalidSignatureException ex)
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsync("refresh_token failed validation");    
                return;                
            }
            catch(Exception ex)
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsync("refresh_token failed validation");
                return;
            }
            user = await GetUserAsync(context, "from_refresh_token");
            accessToken = await GenerateAccessToken(user);

            var response = new
            {
                access_token = accessToken
            };
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));
            return;
        }



private async Task<ApplicationUser> GetUserAsync(HttpContext context, string getUserFrom)
    {
        ApplicationUser user = null;
        if(getUserFrom.Equals("from_username_password"))
        {
            string username = context.Request.Form["username"];
            string password = context.Request.Form["password"];

            if(String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
            {
                throw new ArgumentException("Username/password not sent");
            }

            user = await _userManager.FindByNameAsync(username);
            var result = _userManager.CheckPasswordAsync(user, password);
            if (result.Result == false)
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsync("Invalid username or password");                
            }
            if(!await _userManager.IsEmailConfirmedAsync(user))
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsync("Email address not confirmed");
            }

        }

        else if (getUserFrom.Equals("from_refresh_token"))
        {
            JwtSecurityToken decodedRefreshToken = new JwtSecurityTokenHandler().ReadJwtToken(context.Request.Form["refresh_token"]);
            string uid = decodedRefreshToken.Payload.Claims.FirstOrDefault(x => x.Type == "uid").Value;
            user = await _userManager.FindByIdAsync(uid);
        }

        return user;
    }

错误日志

2017-12-02 19:45:27.171 -05:00 [Information] Request starting HTTP/1.1 POST http://0.0.0.0:5000/api/token application/x-www-form-urlencoded 204
2017-12-02 19:45:27.171 -05:00 [Information] Request starting HTTP/1.1 POST http://0.0.0.0:5000/api/token application/x-www-form-urlencoded 204
2017-12-02 19:45:27.171 -05:00 [Debug] "POST" requests are not supported
2017-12-02 19:45:27.171 -05:00 [Debug] "POST" requests are not supported
2017-12-02 19:45:27.171 -05:00 [Debug] The request has an origin header: '"http://0.0.0.0:8080"'.
2017-12-02 19:45:27.171 -05:00 [Debug] The request has an origin header: '"http://0.0.0.0:8080"'.
2017-12-02 19:45:27.171 -05:00 [Information] Policy execution successful.
2017-12-02 19:45:27.172 -05:00 [Information] Policy execution successful.
2017-12-02 19:45:27.172 -05:00 [Debug] Connection id ""0HL9PS1UOQ2DS"", Request id ""0HL9PS1UOQ2DS:000000C7"": started reading request body.
2017-12-02 19:45:27.172 -05:00 [Debug] Connection id ""0HL9PS1UOQ2E0"", Request id ""0HL9PS1UOQ2E0:0000000B"": started reading request body.
2017-12-02 19:45:27.175 -05:00 [Debug] Connection id ""0HL9PS1UOQ2DS"", Request id ""0HL9PS1UOQ2DS:000000C7"": done reading request body.
2017-12-02 19:45:27.175 -05:00 [Debug] Connection id ""0HL9PS1UOQ2E0"", Request id ""0HL9PS1UOQ2E0:0000000B"": done reading request body.
2017-12-02 19:45:27.450 -05:00 [Information] Entity Framework Core "2.0.0-rtm-26452" initialized '"ApplicationDbContext"' using provider '"Npgsql.EntityFrameworkCore.PostgreSQL"' with options: "None"
2017-12-02 19:45:27.762 -05:00 [Debug] System.InvalidOperationException occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation.
2017-12-02 19:45:27.762 -05:00 [Debug] Entity Framework did not record any exceptions due to failed database operations. This means the current exception is not a failed Entity Framework database operation, or the current exception occurred from a DbContext that was not obtained from request services.
2017-12-02 19:45:27.763 -05:00 [Error] An unhandled exception has occurred while executing the request
System.InvalidOperationException: An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.Set[TEntity]()
   at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.get_UsersSet()
   at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.FindByIdAsync(String userId, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Identity.UserManager`1.FindByIdAsync(String userId)
   at MyProject.TokenProviderMiddleware.<GetUserAsync>d__9.MoveNext() in /home/jrow/Projects/MyProject/MyProject/Middleware/TokenProviderMiddleware.cs:line 220

System.ObjectDisposedException: 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. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'AsyncDisposer'.
   at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.AsyncDisposer.Dispose()
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.<MoveNext>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Linq.AsyncEnumerable.<Aggregate_>d__6`3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.<GetClaimsAsync>d__38.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.AspNetCore.Identity.UserManager`1.<GetClaimsAsync>d__103.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at MyProject.TokenProviderMiddleware.<CreateClaims>d__10.MoveNext() in /home/jrow/Projects/MyProject/MyProject/Middleware/TokenProviderMiddleware.cs:line 239

【问题讨论】:

    标签: c# .net .net-core entity-framework-core asp.net-core-middleware


    【解决方案1】:

    如果你的 UserManager 依赖于一个 dbcontext,它不应该是一个单例。应为每个请求单独创建 dbcontext。使您的 UserManager 瞬态以正确管理 dbcontext。

    【讨论】:

      【解决方案2】:

      通过传递变量而不是每次都全局更新它来修复它。 (从reddit得到答案)。

      public async Task Invoke(HttpContext context, UserManager<ApplicationUser> userManager)
      {
      
          ...
          try
          {
              user = await GetUserAsync(_userManager, context, "from_refresh_token");
          }
          ...
      }
      
      private async Task<ApplicationUser> GetUserAsync(UserManager<ApplicationUser> _userManager, HttpContext context, string getUserFrom)
      {
          //Do stuff with _userManager
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-12-31
        • 2013-03-12
        • 2017-12-15
        • 1970-01-01
        • 1970-01-01
        • 2022-11-17
        • 1970-01-01
        相关资源
        最近更新 更多