10

I have jwt auth:

var messageHandlers = new JwtMessageHandler(_serviceProvider);

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    Events = new JwtBearerEvents
    {
        OnMessageReceived = messageHandlers.OnMessageReceived,
    },
    TokenValidationParameters = tokenValidationParameters
});

The JwtMessageHandler is my custom handler. In the handler I have to make some queries to database, so I pass ServiceProvider and resolve my user service:

public class JwtMessageHandler
{

        private IUserService _userService;  

        public async Task OnMessageReceived(MessageReceivedContext arg)
        {
             //parsing header, get claims from token
             ...
              _userService = (IUserService)arg.HttpContext.RequestServices.GetService(typeof(IUserService));
             var isRoleChanged = await _userService.IsRoleChanged(tokenObject.Subject, rolesFromToken);

            if (isRoleChanged)
            {
                GenerateBadResponse(arg);
                return;
            }

            var canLogin = await _userService.CanLogin(tokenObject.Subject);

            if (!canLogin)
            {
                GenerateBadResponse(arg);
                return;
            }
        }    
}

In the service I make queries:

...
 var user = await _userManager.FindByEmailAsync(email);
 var currentRoles = await _userManager.GetRolesAsync(user);
..

The OnMessageReceived is called for every request. When I have one request on page to the server or I wait one-two seconds before doing something all works fine. But, I have several pages where I make 2-3 simultaneous requests to the server. And, in this case I get error about:

The connection was not closed. The connection's current state is connecting

I understand that problem with multithreading. The JwtMessageHandler is created once when application is started. So, I put the line:

_userService = (IUserService)_serviceProvider.GetService(typeof(IUserService)); 

inside method, before it was located in the constructor. But, It didn't help. Also, I tried to set null to _userService in the end of my method.

How to correctly use in this case?

Taras Shcherban
  • 187
  • 2
  • 11
user348173
  • 8,818
  • 18
  • 66
  • 102
  • How you register `IUserService` in `Startup`? Singleton, scoped, transitional? – Dmitry Dec 19 '16 at 09:18
  • @Dmitry It's scoped. ```services.AddScoped();``` – user348173 Dec 19 '16 at 09:44
  • And you try to use `serviceProvider` created outside "current" scope to obtain scoped service... Very dangerous. Look inside `MessageReceivedContext` - there should be other `ServiceProvider` there, directly or via HttpContext... Ask your service from there. – Dmitry Dec 19 '16 at 10:06
  • @Dmitry Just tried. ```_userService = (IUserService)arg.HttpContext.RequestServices.GetService(typeof(IUserService));```. And, anyway, I get the error. The main problem is catch the error, I get this error randomly. I can work for some time , then only restarting server is help. – user348173 Dec 19 '16 at 10:22
  • Every request to your app creates separate HttpRequest and separate scope and separate instance of `IUserService`, which should use separate `DbContext` instances. Are your DbContext registrations are "usual"? Are you sure you await for `OnMessageReceived` itself? May be something in request processing chain make query to DB while you still executing `IsRoleChanged` ? – Dmitry Dec 19 '16 at 10:29
  • @Dmitry. I have updated code in the question a little. Yes,DbContext registration is usuall, from docs, nothing special. All methods inside UserService are awaited – user348173 Dec 19 '16 at 10:38
  • You have race condition with `_userService` variable. Remove `private IUserService _userService` and do `var _userService =...` inside method. – Dmitry Dec 19 '16 at 10:47
  • I deleted it. Will test a little. – user348173 Dec 19 '16 at 10:55

4 Answers4

22

Trying to use a connection that is already "connecting" - clear sign of some race condition.

  1. Re-check that IUserService is registered with "scope" lifetime, and all it dependencies (userManager, dbContext) too
  2. Do not use IServiceProvider you obtained during app startup for scope-bases services resolution - it is NOT related to current request scope and return instances from "some other universe". Use HttpContext.RequestServices for service resolution.
  3. Check that your are "awaiting" all async methods. If you start second request while still executing first one - you may possibly "catch" dbContext during "connecting" stage.
  4. Your JwtMessageHandler instance is one/single per app. So don't use it's property for storing _userService (remove private IUserService _userService). Instead, use local variable inside OnMessageReceived (var _userService = ...).

You already checked (1), (2) and (3). I think (4) is the last one you need to fix your bug.

Dmitry
  • 16,110
  • 4
  • 61
  • 73
0

@Dmitry's answer pointed me in the right direction. For me, I was having this issue in a MiddleWare on .NETCORE. What solved this issue for me is resolving the IUnitOfWork interface in the Invoke method of my Middleware.

I did something like

public Task Invoke(HttpContext context)
{
    _unitOfWork = (IUnitOfWork)context.RequestServices.GetService(typeof(IUnitOfWork));

       //Some checks            

        var apiKey = context.Request.Headers["X-ApiKey"][0];

        var clientApp = _unitOfWork.ClientApplicationsRepository.Items.FirstOrDefault(s => s.ApiKey == apiKey);

     //Some other code...

    return _next(context);
}
spottedmahn
  • 14,823
  • 13
  • 108
  • 178
Shittu Joseph Olugbenga
  • 6,396
  • 5
  • 26
  • 37
  • This answer is lacking too much context to be helpful to anyone. The reader might assume your IUnitOfWork is an interface implemented by your DbContext subclass, but how you might have been executing the same instance by multiple threads is unclear. – Derek Greer Apr 19 '17 at 23:16
0

I have faced this situation a lot. I have always gotten by by using lock keyword.

lock (_context)
{
      var user = _context.users.first(x => x.Id == userId);
}

This locks the use of the object (i.e. _context) for the current thread and no other thread simultaneously can access this same instance hence no problems whatsoever.

0

In my case I was trying to use Managed Identity and constructed a SqlConnection in ConfigureServices and did AddDbContext<..>(o => o.UseSqlServer(sqlConnection);

That resulted in strange behaviour.

Fixed it by moving the code to the Context OnConfiguring

        var sqlConnection = new SqlConnection(Startup.Configuration.GetConnectionString("xxx"));
        optionsBuilder.UseSqlServer(sqlConnection);
rfcdejong
  • 2,219
  • 1
  • 25
  • 51