CSharp Configuration Handling
Jump to navigation
Jump to search
Introduction
Struggled with the right way to do configuration as there were two approaches so thought I would right down the one that works for me in 2025
Example Redis Service
Create a Config to hold the Setting
namespace Infrastructure.Caching.Configuration;
using Domain.Interfaces.Configuration;
public class CacheOptions : ICacheConfig
{
public required string Host { get; set; }
public required int Port { get; set; }
public required string Password { get; set; }
public string InstanceName { get; set; } = "DvdrentalApi_";
public TimeSpan DefaultTtl { get; set; } = TimeSpan.FromMinutes(5);
public string ConnectionString => $"{Host}:{Port},password={Password},ssl=False,abortConnect=False";
}
Create a Service to do it
namespace Infrastructure.Caching.Services;
using System.Text.Json;
using Domain.Interfaces.Caching;
using Domain.Interfaces.Configuration;
using Microsoft.Extensions.Caching.Distributed;
public class RedisCacheService(IDistributedCache cache, ICacheConfig config) : ICacheService
{
private readonly IDistributedCache _cache = cache;
private readonly ICacheConfig _config = config;
public async Task<T?> GetAsync<T>(string key, CancellationToken ct = default)
{
var namespacedKey = $"{_config.InstanceName}{key}";
var cached = await _cache.GetStringAsync(namespacedKey, ct);
return cached is null ? default : JsonSerializer.Deserialize<T>(cached);
}
public async Task SetAsync<T>(string key, T value, TimeSpan? ttl = null, CancellationToken ct = default)
{
var namespacedKey = $"{_config.InstanceName}{key}";
var serialized = JsonSerializer.Serialize(value);
await _cache.SetStringAsync(
namespacedKey,
serialized,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = ttl ?? _config.DefaultTtl
},
ct);
}
public Task RemoveAsync(string key, CancellationToken ct = default)
{
var namespacedKey = $"{_config.InstanceName}{key}";
return _cache.RemoveAsync(namespacedKey, ct);
}
}
Add IServiceCollection Extension for Startup
The main thing with Redis was when it failed, it caused the connection string to be printed in the error. If you use ConfigurationOptions (part of AddStackExchangeRedisCache) this is not the case.
private static IServiceCollection AddCachingService(this IServiceCollection services, IConfiguration config)
{
// Bind CacheOptions from configuration
var cacheConfig = config.GetSection("Cache").Get<CacheOptions>() ??
throw new InvalidOperationException("Cache configuration section 'Cache' is missing or invalid.");
// Register ICacheConfig for DI
services.AddSingleton<ICacheConfig>(cacheConfig);
// Register Redis as IDistributedCache using ConfigurationOptions
services.AddStackExchangeRedisCache(options =>
{
Console.WriteLine("Configuring Redis Cache");
var redisOptions = new ConfigurationOptions
{
EndPoints = { { cacheConfig.Host, cacheConfig.Port } },
Password = cacheConfig.Password,
AbortOnConnectFail = false,
Ssl = false
};
options.ConfigurationOptions = redisOptions;
options.InstanceName = cacheConfig.InstanceName;
});
// Register RedisCacheService
services.AddSingleton<ICacheService, RedisCacheService>();
return services;
}
Make an appsetting.json
Here is my example appsetting.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http": "Warning",
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"Cache": {
"host": "192.168.1.220",
"port": 6379,
"password": "MOVED TO USER SECRETS",
"InstanceName": "DvdrentalApi_",
"DefaultTtl": "00:05:00"
}
}
Put password in User-Secrets
In the project directory for the binary. (Note if you are using ! or other characters you will need to escape with \!
dotnet user-secrets init
dotnet user-secrets set Cache:Password NotSaying!!!
.NET creates an entry in your project. This is how it knows to use user secrets
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>blah-blah-blah</UserSecretsId>
</PropertyGroup>
Putting into k8s
Here is an example of the k8s config map for environment variables.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deployment
spec:
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: yourregistry/api:latest
env:
- name: Cache__Password
value: "blahblah"
Order of Play
This is the order parameters are read
appsettings.json
↓
appsettings.{Environment}.json
↓
User Secrets
↓
Environment Variables
↓
Command-line arguments