停止在 .NET 中错误使用 IOptions!

发布: (2025年12月27日 GMT+8 02:41)
6 min read
原文: Dev.to

Source: Dev.to

(请提供您希望翻译的正文内容,我将为您翻译成简体中文。)

📌 IOptions简易版

可以把它想象成: 一张写好后再也不改动的便利贴。

它的作用: 在应用启动时加载设置,仅此而已。

示例

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;
    }
}

适用场景: 永不改变的设置——例如 SMTP 服务器、数据库 URL、默认发件人邮箱。

注意事项: 如果修改了配置文件,需要重启应用才能看到更改。


📌 IOptionsSnapshot面向请求的实现

可以把它想象成: 每次有人找你时,你都检查一下自己的消息。

它的作用: 为每一次进入的 HTTP 请求获取最新的设置。

示例

public class NotificationController : ControllerBase
{
    private readonly IOptionsSnapshot<NotificationSettings> _prefs;

    public NotificationController(IOptionsSnapshot<NotificationSettings> prefs)
    {
        _prefs = prefs;
    }

    [HttpPost("send")]
    public IActionResult SendNotification()
    {
        // 为每个请求获取最新的偏好设置
        var enableEmail = _prefs.Value.EnableEmailNotifications;
        var enableSms   = _prefs.Value.EnableSmsNotifications;

        // 根据当前偏好发送通知
        // …
        return Ok();
    }
}

适用场景: 用户偏好、通知开关、A/B 测试——即不同请求可能需要不同设置的情况。

需要注意: 仅在 Web 请求中有效,后台任务中不可用,并且会在每个请求时读取配置。


📌 IOptionsMonitor实时更新版

想象它是: 打开实时通知——一有变化你立刻知道。

它的作用: 监视你的配置文件,在你更改时自动更新(无需重启)。

示例

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);
        }
    }
}

适用场景: 后台作业、计划任务,或任何需要实时更新而不重启的情况。

注意点: 设置稍微复杂,且会占用稍多的内存。


🎯 如何选择?

问自己一个简单的问题:“我的配置在应用运行时会改变吗?”

答案推荐选项
IOptions – 简单且快速
是用于 web 请求 还是 后台工作
Web 请求IOptionsSnapshot
后台工作IOptionsMonitor

⚠️ 我犯过的错误(让你免于重蹈)

错误为什么是问题
在后台服务中使用 IOptionsSnapshot它仅适用于作用域的 Web 请求
IOptionsSnapshot.Value 存储在变量中失去“每次请求都新鲜”的优势
忘记使用 IOptionsMonitor 监听 OnChange你将不知道配置何时更改
未验证设置当配置错误时,应用可能在随机时间崩溃

💡 我早该知道的酷技巧

技巧 #1 – 同类型的多个配置

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");

技巧 #2 – 启动时验证配置

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"));

使用(例如,在后台服务中)

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"));

后台服务 – 需要实时更新!

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 端点 – 需要每次请求都获取最新数据

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();
    }
}

🎓 记住这个

选项何时使用类比
IOptions写一次,读永久纹身——设定后永久不变
IOptionsSnapshot每次请求都有新数据每天早上查看天气
IOptionsMonitor配置更改时实时更新社交媒体动态实时更新

你以前用过这些吗?
哪一个让你最困惑?在下面留言吧!👇

如果这对你有帮助,请保存以备后用——凌晨 2 点调试时你会感谢自己的!

Back to Blog

相关文章

阅读更多 »