appsettings.json is great for things developers change: connection strings, logging levels. It's a poor fit for things a store owner changes: the contact email, whether PayPal is enabled, the shipping fee, the SendGrid key. Asking the owner to edit JSON and redeploy for each of those isn't realistic.
So eCommerceMVCCore, my ASP.NET Core store, keeps those settings in a database table, editable from Dashboard → Configurations. Here's the design, with simplified code.
A simple key/value table
public class Configuration
{
public int Id { get; set; }
public string Key { get; set; } = "";
public string? Value { get; set; }
public string? Description { get; set; } // shown in the admin UI
public bool IsSecret { get; set; }
}
Each row carries a description, so the admin page can explain what "EnablePayPal" or "PayPalClientSecret" does right next to the field. A unique index on Key prevents duplicates.
A cached settings service
Reading the database on every request would be wasteful, so a small service loads all settings once and caches them:
public class SettingsService(AppDbContext db, IMemoryCache cache) : ISettingsService
{
const string CacheKey = "site-settings";
public async Task<IReadOnlyDictionary<string, string?>> GetAllAsync() =>
(await cache.GetOrCreateAsync(CacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
return await db.Configurations.AsNoTracking()
.ToDictionaryAsync(c => c.Key, c => c.Value);
}))!;
public async Task<string?> GetAsync(string key) =>
(await GetAllAsync()).GetValueOrDefault(key);
public async Task<bool> GetBoolAsync(string key) =>
bool.TryParse(await GetAsync(key), out var b) && b;
public void Invalidate() => cache.Remove(CacheKey);
}
When the admin saves a setting, call Invalidate(). The next request reloads fresh values, and the change is live immediately.
The 30-minute expiry is a safety net. If you run more than one instance, a memory cache is per instance, so other instances pick up the change when their cache expires. For instant changes across a farm, use a distributed cache or a change-notification message.
Never send secrets back to the browser
Some settings are secrets: the PayPal client secret, the SendGrid API key. The admin page should let you set them, but never show them:
- When rendering the form, show a masked placeholder such as
••••••••for secret rows, never the real value. - When saving, treat an empty secret field as "keep the current value".
foreach (var input in form.Settings)
{
var row = await db.Configurations.SingleAsync(c => c.Key == input.Key);
if (row.IsSecret && string.IsNullOrWhiteSpace(input.Value))
continue; // left empty: keep the existing secret
row.Value = input.Value?.Trim();
}
await db.SaveChangesAsync();
settings.Invalidate();
That way a secret never travels to the browser, never shows in the page source, and isn't wiped by accidentally saving the form.
Encrypting secret values at rest is a good next step. ASP.NET Core's Data Protection API (IDataProtector.Protect and Unprotect) does it in a few lines, as long as the key ring is persisted somewhere that survives restarts.
See the settings page: the eCommerceMVCCore admin dashboard lists each setting with its description, and secrets stay masked.
Who can change what
Settings change how the store takes money, so the page is restricted to the Administrator role, not Moderators:
[Authorize(Roles = "Administrator")]
public class ConfigurationsController : Controller { ... }
The same idea for translations
UI translations work the same way: a table of key, culture and text, loaded into a cache and edited from the dashboard. Admins can fix a typo in the Arabic checkout page, or import a whole language pack, without touching code.
What stays in appsettings.json
Anything needed before the database is reachable stays in configuration: the connection string, the database provider, the JWT signing key and logging. Database settings are for the business; file settings are for the infrastructure.
FAQ
Should settings live in appsettings.json or the database?
Infrastructure settings that are needed before the database is reachable, such as connection strings, signing keys and logging, belong in configuration. Business settings that the owner changes belong in the database.
How do I change ASP.NET Core settings without restarting?
For files, reloadOnChange plus IOptionsMonitor picks up edits. For database settings, cache them and invalidate the cache when they're saved, as shown above.
How should I store API keys in ASP.NET Core?
User secrets in development; environment variables or a key vault in production. If keys live in the database, encrypt them with the Data Protection API and never send them back to the browser. In eCommerceMVCCore, the PayPal and SendGrid secrets are masked in the dashboard after saving.
Get the source code
Database-backed settings and translations, editable from the admin dashboard with secrets masked, are part of eCommerceMVCCore. Get the ASP.NET Core 10 e-commerce source code.
Where do you draw the line between file configuration and database settings? I'd like to hear your rule.
Top comments (0)