Home > Blockchain >  .NET API threading issue (DBContext)
.NET API threading issue (DBContext)

Time:02-17

I am implementing an API and as part of it I have setup a custom .Net Middleware service extension UseRequestLoggingModdlewareExtension() that it run between the following:

app.UseHttpsRedirection();

app.UseRequestLoggingModdlewareExtension();

app.UseRouting();

The code is simple, and just logs the output of the request into a custom table.

public async Task InvokeAsync(HttpContext httpContext)
        {
            var stopAccess = _keyManager.getKeyValue("stopImmediateAccess");

            if (!Convert.ToBoolean(stopAccess))
            {
                await _next(httpContext);

                var loggingLevel = _keyManager.getKeyValue("loggingLevel");

                if (loggingLevel != null)
                {
                    if (loggingLevel.ToLower() == "information")
                    {
                        var userIdClaim = httpContext.User.FindFirst("userid")?.Value;
                        int? userId = null;
                        if(userIdClaim != null)
                        {
                            userId = Int32.Parse(userIdClaim);
                        }
                        var logging = new ApiRequestLogging
                        {
                            userId = userId,
                            remoteIp = httpContext.Connection.RemoteIpAddress.ToString() == "::1" ? "localhost" : httpContext.Connection.RemoteIpAddress.ToString(),
                            userAgent = httpContext.Request.Headers["User-Agent"].ToString(),
                            requestMethod = httpContext.Request.Method,
                            requestUrl = httpContext.Request.Path,
                            queryString = httpContext.Request.QueryString.ToString(),
                            requestHeaders = String.Join(",", httpContext.Request.Headers),
                            responseCode = httpContext.Response.StatusCode,
                            responseHeaders = String.Join(",", httpContext.Response.Headers),
                            createdDt = DateTime.Now
                        };
                        _logging.LogApiRequest(logging);
                    }
                }
            }
        }

Where I am struggling is with some errors regarding some issues with the DBContext.

System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

The error is appearing twice, at both lines where the _keyManager service is called. The keyManager service is simply doing the following:

public string getKeyValue(string keyName)
        {
            var value = _context.keyManagement.Where(k => k.keyName == keyName).Select(v => v.keyValue).FirstOrDefault();
            return value;
        }

I have a suspicion that it could be something to do with the 'await' and the aschronousness of the code, however I have tried multiple combinations and cannot seem to bypass this issue.

CodePudding user response:

Do you implement the IMiddleware interface i.e. something like this RequestLoggingMiddleware: IMiddleware This will resolve your middleware through the IMiddlewareFactory like a scoped service and inject the other dependant services. Your middleware ctor should be something like RequestLoggingMiddleware(IKeyManagerService keyManager) This way the middleware will be activated per client request i.e. scoped and not in the normal way as a singleton. Providing scoped middleware instances per request will allow you to use short-lived ApplicationDbContext in the middleware or its dependent services:

public RequestLoggingMiddleware(ApplicationDbContext  db)
{
    _db = db;
}

or in your case more like

public class RequestLoggingMiddleware: IMiddleware 
{
   public RequestLoggingMiddleware(IKeyManagerService keyManager)
   {
       _keyManager = keyManager;
   }
}

public KeyManagerService(ApplicationDbContext  db) 
{
    _db = db;
} 

services.AddScoped<IKeyManagerService, KeyManagerService>()

This way the ApplicationDbContext used by keyManager service will be created per request and disposed of after the request is completed. Of course, the IKeyManagerService should be registered as a scoped service as well.

CodePudding user response:

Thats why i like to use IDisposable interface with DbContext.

public string getKeyValue(string keyName)
{
    string value = null;
    using(var _cnx = new DbContext())
    {
            value = _cnx.keyManagement.Where(k => k.keyName == keyName).Select(v => v.keyValue).FirstOrDefault();
            
    }       
    return value;
}

  • Related