Skip to content

Local Redis cache isolation

TL;DR

By default every web project's Web.config points the Redis cache at the shared test cache (raklet-test-cache-ng.redis.cache.windows.net). Local dev, the test environment, and CI all read and write the same keys on that one instance. That causes cross-environment cache bleed — most visibly a broken org logo on local pages.

Fix: run a local Redis and point your machine at it.

pwsh scripts/dev/setup-local-redis.ps1

Then recycle your app pool (or iisreset). No application code changes are required, and nothing you do here is committed.

Why the logo breaks (the motivating bug)

SessionService.OrganisationInfo(permalink) (Services/SessionService.cs) caches public org info under the key "{permalink}-OrgInfoForPublicPages" for 12 hours:

model.LogoUrl = ConfigurationManager.AppSettings["StoragePath"] + organisation.LogoUrl;
CacheService.Set(cacheKey, model.ToJson(), TimeSpan.FromHours(12));

Two facts make this leak across environments:

  1. The cache key has no environment namespace — it is just gercek-OrgInfoForPublicPages, identical in local, test, and CI.
  2. CacheService connects to whatever Raklet-Cache-Connectionstring points at, and the committed Web.config value is the shared test cache. So local and test share the same Redis database.

The cached JSON bakes in the database's organisation.LogoUrl value. The key is purged on any org-settings change (WebJobs, CRM, and the V2 API all call CacheService.Purge("{permalink}-OrgInfoForPublicPages")) — again, against the shared Redis. So the sequence is:

  1. Something on test/CI touches a gercek-permalinked org → purges the shared key.
  2. The next request to hit that permalink — possibly from test/CI, not your local machine — re-populates the key from that environment's database.
  3. If test's row has an empty/default logo, or a logo blob that doesn't exist, your local page now renders test's broken <img src> until the next purge or the 12-hour TTL expires.

That is why the logo "breaks again" intermittently with no local change.

StoragePath is identical (rakletlocalfiles) across all configs, so the differentiator is purely the DB-sourced LogoUrl carried by whichever environment wrote the key last.

What the setup script does

scripts/dev/setup-local-redis.ps1:

  1. Checks whether a Redis server is already answering on localhost:6379.
  2. If not, installs Redis on Windows via winget install Redis.Redis (registers an auto-starting Windows service on port 6379). Pass -SkipInstall to skip this and only write config.
  3. For each local web project (Application, Raklet.Backend, Raklet.Admin, Raklet.Api, Raklet.Login, Raklet.Crm), ensures appSettings.local.config contains a Raklet-Cache-Connectionstring override pointing at localhost:6379. It creates a minimal overlay file if none exists, injects the key if the file exists without it, and leaves it alone if the key is already present.
  4. For each of the four WebJobs projects, writes the same override both at the project root and directly into any existing bin\Debug / bin\Release output folder next to the built .exe.config. WebJobs resolve their appSettings.local.config overlay relative to the build output, not the project root, so an already-built or currently-running WebJob only picks up the change once its bin\<Configuration> copy is written and the process is restarted. Before writing, the script verifies that App.config loads the overlay and the project file copies it to the build output; it fails with a concrete wiring error instead of reporting successful isolation when either prerequisite is missing.

appSettings.local.config is gitignored, so your override is never committed. The file= attribute on <appSettings> (Web.config for the web projects, App.config for the WebJobs) makes it an overlay: this key overrides the committed test-cache value; every other key falls through unchanged.

The override, by hand

If you prefer to do it manually, add this to each project's appSettings.local.config (copy from appSettings.local.config.template):

<add key="Raklet-Cache-Connectionstring"
     value="localhost:6379,abortConnect=False,connecttimeout=15000,synctimeout=15000" />

Three things differ from the committed test string — all required or it won't connect:

Test cache Local Redis
Port 6380 6379
TLS ssl=True (omitted)
Auth password=... (none)

Choosing a Redis server

Option Install Notes
Redis on Windows (default) winget install Redis.Redis One-liner, auto-starts as a service on 6379. Older build, but the cache only does GET/SET/EXPIRE, so it's fine for dev.
Memurai Developer Manual MSI from https://www.memurai.com/get-memurai Free for dev, current Redis 7 compatibility, polished Windows service. Not in winget.
WSL / Docker apt install redis-server / docker run -p 6379:6379 redis Only worthwhile if you already run WSL or Docker; otherwise heavier than the above.

Any of them listens on localhost:6379 with no TLS and no password, so the same override string works for all.

Scope and a note for the future

This isolates the cache only; your local app still uses the shared dev database and storage from Web.config unless you override those too. The logo is served by the Application project, so isolating that one resolves the immediate bug; the script covers the other web projects and the four WebJobs for completeness.

A more durable, code-level fix would be to namespace cache keys per environment (e.g. prefix every CacheService key with an Environment appSetting) so a shared Redis can never collide. That's a broader change to CacheService and out of scope here — tracked as a follow-up.