skip to Main Content

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.

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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;
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search