Why IDistributedCache.RemoveAsync(HttpContext.Session.Id) Doesn't Work in ASP.NET Core
If you’re using ASP.NET Core Session with MySQL via Pomelo.Extensions.Caching.MySql and trying to terminate a specific user’s session from the backend, you’ve probably run into the same issue I did.
After roughly 3–4 hours of research, I finally found the root cause. In this article, I’ll walk you through both the journey and the solution step by step.
The Scenario
I’m storing ASP.NET Core Sessions in MySQL:
builder.Services.AddDistributedMySqlCache(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("SessionConnection");
options.SchemaName = "sessionsdb";
options.TableName = "AppSessions";
});builder.Services.AddSession();After a user logs in, I save their Session ID to the database:
var sessionId = HttpContext.Session.Id;Later, when I want to force-logout a user from the admin panel or backend, I run:
await _distributedCache.RemoveAsync(sessionId);My expectation was simple:
Delete the session from the database → the user gets automatically logged out.
But nothing happened.
Initial Assumption
My first thought was that Pomelo was modifying the Session ID somehow. Maybe it was:
- Hashing it
- Adding a prefix
- Encoding it
So my initial research focused entirely on that.
Inspecting the Database
Pomelo requires us to create the following table:
AppSessions
- Id
- Value
- ExpiresAtTime
- SlidingExpirationInSeconds
- AbsoluteExpirationMy first assumption was:
AppSessions.Id == HttpContext.Session.Id
If that were true, then RemoveAsync(HttpContext.Session.Id) should have worked.
But it didn’t.
Looking at Pomelo’s Source Code
I suspected Pomelo was the culprit, but after inspecting its source code, I realized it’s just a standard IDistributedCache implementation.
The logic is roughly:
SetAsync(string key, byte[] value)Pomelo simply writes the key it receives into the Id column of the database.
That means:
- Pomelo doesn’t generate any key
- It doesn’t hash or encode anything
- It doesn’t produce Session IDs
At this point, the problem was not Pomelo.
So Who Generates That Key?
It was time to dive into Microsoft’s Session source code.
The first class I looked at was DistributedSession.
The constructor caught my attention:
DistributedSession(
IDistributedCache cache,
string sessionKey,
...
)Notice: It’s sessionKey, not sessionId :)
Where Does sessionKey Come From?
Next, I looked at SessionMiddleware.
Get Musa KÖŞKER’s stories in your inbox
Join Medium for free to get updates from this writer.
The flow is roughly:
Request → Read Cookie → Decode Cookie → Generate sessionKey → Create DistributedSessionSo the key passed into DistributedSession is a different value called sessionKey.
The Biggest Misconception
My assumption was:
HttpContext.Session.Id == AppSessions.Id
But the actual flow is:
Browser → .AspNetCore.Session Cookie → SessionMiddleware → sessionKey → IDistributedCache → Pomelo → AppSessions.IdIn other words:
The Id in the database is NOT Session.Id. It's a cache key generated internally by the framework.
The Real Problem
I was running:
await _distributedCache.RemoveAsync(HttpContext.Session.Id);But RemoveAsync() expects the sessionKey, not the Session.Id.
So I was trying to delete a record using the wrong key, and that’s why nothing happened.
Why Can’t We Access sessionKey?
Because Microsoft doesn’t expose it as a public API.
There’s no property like:
HttpContext.Session.SessionKey
// or
HttpContext.Session.CacheKeyThe framework keeps it entirely private.
The Solution
We can use Reflection to read the private field:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Session;
using System.Reflection;
public static class SessionExtensions
{
public static string? GetSessionCacheKey(this HttpContext context)
{
if (context.Session is not DistributedSession session)
return null;
var field = typeof(DistributedSession)
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.FirstOrDefault(f =>
f.FieldType == typeof(string) &&
f.Name.Contains("sessionKey", StringComparison.OrdinalIgnoreCase));
return field?.GetValue(session)?.ToString();
}
}Now we can retrieve the actual cache key:
var cacheKey = HttpContext.GetSessionCacheKey();
await _distributedCache.RemoveAsync(cacheKey);This deletes the record from the AppSessions table, and the user will be automatically logged out on their next request.
The Most Important Thing I Learned
At the end of this research, I realized:
- The bug was not in Pomelo
- The culprit was ASP.NET Core Session’s internal architecture
TL;DR
HttpContext.Session.Idis not the cache keyPomelo.Extensions.Caching.MySqldoesn't generate any key- The
Idin the database is thesessionKeygenerated by the framework IDistributedCache.RemoveAsync()only works with that cache key- Since the framework doesn’t expose this value publicly, we need to use Reflection
Important Note
This solution accesses private fields inside the framework, so it may break in future .NET versions.
I wrapped this logic inside a small extension method so that if Microsoft changes the private field name, I only need to update one file.
Conclusion
Sometimes we spend hours looking for a bug in the wrong place.
I initially blamed Pomelo.Extensions.Caching.MySql, but the real issue was deep inside ASP.NET Core Session's internal architecture.
If you’re:
- Using ASP.NET Core Session
- Storing sessions in MySQL
- Trying to terminate user sessions from the backend
— I hope this article saves you a few hours of frustration.
🇹🇷 Turkish version: https://medium.com/@musakosker/asp-net-core-pomelo-extensions-caching-mysql-removeasync-session-id-neden-ba%C5%9Far%C4%B1s%C4%B1z-oluyor-374fbe73d5c4




