question

SureshKumar-4377 avatar image
0 Votes"
SureshKumar-4377 asked SureshKumar-4377 edited

Counting requests per session in ASP.NET Core

I am trying to count the number of requests a client makes to a controller action in a single session. I am storing the count in session and incrementing during each request. This works well if the client sends the requests serially but seems to not work when the client sends concurrent requests. Below is my controller:

 [ApiController]
 [Route("[controller]")]
 [Produces("application/json")]
 public class SessionRequestCounterController : ControllerBase
 {
     private readonly static string REQUEST_COUNTER = "REQUEST_COUNTER";
     private static readonly object _lockObj = new object();
    
     public SessionRequestCounterController(ILogger<SessionRequestCounterController> logger)
     {
         _logger = logger;
     }
    
     [HttpGet]
     public IActionResult Get()
     {
         lock (_lockObj)
         {
             int? count = HttpContext.Session.GetInt32(REQUEST_COUNTER);
             if (count == null)
             {
                 count = 1;
                 HttpContext.Session.SetInt32(REQUEST_COUNTER, count.Value);
             }
             else
             {
                 count++;
                 HttpContext.Session.SetInt32(REQUEST_COUNTER, count.Value);
             }
             return Ok(count.Value);
         }            
     }
 } 

The HTML with JavaScript sending requests serially and concurrently is below:

 <html>
 <head>
     <script>
         const host = "localhost:44307";
         const requestUrl = `https://${host}/SessionRequestCounter`;
         const options = {
             method: "GET",
             headers: {
                 "Content-Type": "application/json",
             },
             credentials: "include"
         };
    
         async function sendRequestsSerially() {
             for (let i = 0; i < 15; i++) {
                 let res = await fetch(
                     requestUrl,
                     options
                 );
                 console.log(await res.json());
             }
         }
    
         async function sendRequestsParallelly() {
             const reqs = [];
             for (let i = 0; i < 15; i++) {
                 reqs.push(fetch(
                     requestUrl, options
                 ).then((res) => res.json()));
             }
             const allData = Promise.all(reqs);
             allData.then((res) => console.log(res));
         }
     </script>
 </head>
 <body>
     <div>
         <button onclick="sendRequestsSerially()">SendRequest Serially</button>
         <button onclick="sendRequestsParallelly()">SendRequest Parallelly</button>
     </div>
 </body>
 </html>

When the requests are sent serially, we can see the request count in console like 1, 2, 3, 4, 5,...
When the requests are send concurrently, we can see the request count increments randomly like 1,1,2,2,3,...

It does seem like the HTTP requests are queued and each request gets a copy of the session data and hence concurrent requests don't work as expected. I couldn't find any documentation which clarifies the above behaviour. Appreciate any pointers or explanation of this behaviour.

NOTE: I have tried this with ASP.NET Core 3.0, 3.1, 5.0 and 6.0 and all have the same behaviour.

dotnet-aspnet-core-webapi
· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Session is asynchronous in ASP.NET Core unlike ASP.NET where the requests are serialized when Session is enabled. Use a or configure a synchronous Session provider. However, this comes at a performance cost that Core specifically addressed.

Load session state asynchronously

My initial though is a fire and forget (asynchronous) log that write the user's name on each request. Do a count of the user distinct users to find the count. I'm not sure how long this log should exist. Since your original approach is using Session I assume this is a short lived count???

0 Votes 0 ·

Thanks for your response. I looked through the link you provided and if I understand correctly, the following statement from the article seems to suggest the default behaviour is synchronous unless LoadAsync is called before storing or getting data from the session. In my case, I never call LoadAsync so it should be synchronous and I am really not concerned about performance at the moment.

The default session provider in ASP.NET Core loads session records from the underlying IDistributedCache backing store asynchronously only if the ISession.LoadAsync method is explicitly called before the TryGetValue, Set, or Remove methods. If LoadAsync isn't called first, the underlying session record is loaded synchronously, which can incur a performance penalty at scale

There are several ways I could solve this but it is very involved and not straight forward solution. For e.g. cleaning up on session expiry etc. I would like to first confirm if what I am trying to do using session is not possible before I looking at rolling out a custom implementation.








0 Votes 0 ·
AgaveJoe avatar image AgaveJoe SureshKumar-4377 ·

Let me say this another way, the entire ASP.NET Core HTTP pipeline is asynchronous. The requests are not serialized like ASP.NET when Session is enabled.

Secondly, logging the requests by user is trivial. Again, I would use a fire and forget pattern. But this assumes the design uses standard Web API authentication/authorization.

Lastly, using Session to track uses in Web API is possible but unusual. Unfortunately, you have not explained the use case or any configuration so we have no idea what you're doing.

0 Votes 0 ·
Show more comments

0 Answers