0

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.

3
  • 4
    In general, it is a common beginners' mistake to not realize that database contexts are lightweight objects that are supposed to be created and destroyed for every query or group of related queries (possibly a session) towards databases. They are not supposed to be hanging around for long or to be used across threads. Consider whether this is the cause of your problem. (Follow the link in the error message.) Commented Feb 13, 2022 at 9:35
  • 3
    Can you show your service injection configuration ? This is probably caused by the keymanager being scoped, but middlewares are singleton. See learn.microsoft.com/en-us/aspnet/core/fundamentals/… Commented Feb 13, 2022 at 9:36
  • Async call issue. See this - stackoverflow.com/questions/44237977/…. In the same time, try to use using statement for db context as this confirms every db connection opened is closed properly. Commented Feb 13, 2022 at 9:51

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

Comments

0

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;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.