Stop Using IOptions Wrong in .NET!
Source: Dev.to
📌 IOptions – The Simple One
Think of it as: a sticky note you write once and never change.
What it does: loads your settings when the app starts, and that’s it.
Example
public class NotificationSettings
{
public string SmtpServer { get; set; }
public int Port { get; set; }
public string DefaultSender { get; set; }
}
public class NotificationService
{
private readonly NotificationSettings _settings;
public NotificationService(IOptions<NotificationSettings> options)
{
_settings = options.Value;
}
}
Use it for: settings that never change – e.g., SMTP server, database URL, default sender email.
The catch: if you change the config file you must restart the app to see the changes.
📌 IOptionsSnapshot – The Request‑Friendly One
Think of it as: checking your messages every time someone asks you.
What it does: gets fresh settings for every HTTP request that comes in.
Example
public class NotificationController : ControllerBase
{
private readonly IOptionsSnapshot<NotificationSettings> _prefs;
public NotificationController(IOptionsSnapshot<NotificationSettings> prefs)
{
_prefs = prefs;
}
[HttpPost("send")]
public IActionResult SendNotification()
{
// Gets fresh preferences for each request
var enableEmail = _prefs.Value.EnableEmailNotifications;
var enableSms = _prefs.Value.EnableSmsNotifications;
// Send based on current preferences
// …
return Ok();
}
}
Use it for: user preferences, notification toggles, A/B testing—situations where different requests might need different settings.
The catch: works only in web requests, not in background jobs, and reads the config on every request.
📌 IOptionsMonitor – The Live‑Updates One
Think of it as: having live notifications turned ON – you know immediately when something changes.
What it does: watches your config file and updates automatically when you change it (no restart needed).
Example
public class NotificationBackgroundService : BackgroundService
{
private readonly IOptionsMonitor<NotificationLimits> _monitor;
public NotificationBackgroundService(IOptionsMonitor<NotificationLimits> monitor)
{
_monitor = monitor;
// Runs automatically when config changes!
_monitor.OnChange(thresholds =>
{
Console.WriteLine("Notification thresholds updated!");
UpdateNotificationRules(thresholds);
});
}
protected override async Task ExecuteAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
// Use current threshold values
var maxPerHour = _monitor.CurrentValue.MaxNotificationsPerHour;
await CheckAndSendNotifications(maxPerHour);
await Task.Delay(TimeSpan.FromMinutes(5), token);
}
}
}
Use it for: background jobs, scheduled tasks, or any scenario where you need live updates without restarting.
The catch: a bit more complex to set up and uses slightly more memory.
🎯 How Do I Choose?
Ask yourself one simple question: “Will my config change while the app is running?”
| Answer | Recommended option |
|---|---|
| No | IOptions – simple and fast |
| Yes | Is it for web requests or background work? |
| Web requests | IOptionsSnapshot |
| Background work | IOptionsMonitor |
⚠️ Mistakes I’ve Made (so you don’t have to)
| Mistake | Why it’s a problem |
|---|---|
Using IOptionsSnapshot in a background service | It only works for scoped web requests |
Storing IOptionsSnapshot.Value in a variable | You lose the “fresh‑every‑request” benefit |
Forgetting to listen to OnChange with IOptionsMonitor | You won’t know when the config changes |
| Not validating settings | The app can crash at random times when the config is wrong |
💡 Cool Tricks I Wish I Knew Earlier
Trick #1 – Multiple configs of the same type
services.Configure<EmailSettings>("Email", config.GetSection("Email"));
services.Configure<SmsSettings>("SMS", config.GetSection("SMS"));
services.Configure<PushSettings>("Push", config.GetSection("Push"));
// Later in your code:
var emailSettings = _options.Get<EmailSettings>("Email");
var smsSettings = _options.Get<SmsSettings>("SMS");
Trick #2 – Validate your config on startup
services.AddOptions<NotificationSettings>()
.Validate(settings => !string.IsNullOrEmpty(settings.SmtpServer),
"Hey! You forgot the SMTP Server!")
.Validate(settings => settings.Port > 0 && settings.Port (Configuration.GetSection("NotificationLimits"));
Consume (e.g., in a background service)
public class NotificationWorker : BackgroundService
{
private readonly IOptionsMonitor<NotificationLimits> _limits;
public NotificationWorker(IOptionsMonitor<NotificationLimits> limits)
{
_limits = limits;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var limits = _limits.CurrentValue;
// Use limits.MaxPerUser and limits.TimeWindowHours …
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
{
"NotificationLimits": {
"MaxPerUser": 5,
"EnableEmailNotifications": true,
"EnableSmsNotifications": false
}
}
// When starting your app
builder.Services.Configure<NotificationLimits>(
builder.Configuration.GetSection("NotificationLimits"));
Background service – needs live updates!
public class NotificationQueueService : BackgroundService
{
private readonly IOptionsMonitor<NotificationLimits> _monitor;
public NotificationQueueService(IOptionsMonitor<NotificationLimits> monitor)
{
_monitor = monitor;
}
protected override async Task ExecuteAsync(CancellationToken token)
{
// Update limits immediately when config changes
_monitor.OnChange(limits =>
{
Console.WriteLine($"Notification limits updated to {limits.MaxPerUser} per user");
UpdateQueueProcessor(limits);
});
while (!token.IsCancellationRequested)
{
await ProcessNotificationQueue(_monitor.CurrentValue);
await Task.Delay(TimeSpan.FromSeconds(10), token);
}
}
}
API endpoint – needs fresh data per request
public class NotificationController : ControllerBase
{
private readonly IOptionsSnapshot<NotificationLimits> _limits;
public NotificationController(IOptionsSnapshot<NotificationLimits> limits)
{
_limits = limits;
}
[HttpPost("send")]
public async Task<IActionResult> SendNotification([FromBody] NotificationRequest request)
{
var maxPerUser = _limits.Value.MaxPerUser;
if (await CheckUserNotificationCount(request.UserId) >= maxPerUser)
return BadRequest("Notification limit reached");
// Each request gets current limits
await SendNotificationAsync(request);
return Ok();
}
}
🎓 Remember This
| Option | When to Use | Analogy |
|---|---|---|
| IOptions | Write‑once, read‑forever | A tattoo – permanent after you set it |
| IOptionsSnapshot | Fresh data on every request | Checking the weather each morning |
| IOptionsMonitor | Live updates when configuration changes | Your social‑media feed updating in real time |
Have you used these before?
Which one confused you the most? Drop a comment below! 👇
If this helped you, save it for later – you’ll thank yourself when you’re debugging at 2 AM!