如何让IOptionsSnapshot.Get()遇无效名称时抛出异常?
当传入未注册的名称时,IOptionsSnapshot.Get(string? name)会返回一个“默认”实例
当前应用仅注册了一个名为GovernanceServiceOptions.UnassignedApplications的命名选项。调用IOptionsSnapshot<T>.Get()传入无效名称时,方法会返回默认实例(比如new GovernanceServiceOptions())。希望让该方法在传入无效名称时抛出异常,该如何实现?
解决方案
要实现这个需求,我们可以通过包装IOptionsSnapshot<T>并跟踪已注册的选项名称,在调用Get方法时校验名称合法性,未注册则抛出异常。
1. 实现已注册选项名称跟踪器
创建一个单例服务,用于记录所有注册过的命名选项名称:
public class RegisteredOptionsTracker { private readonly Dictionary<Type, HashSet<string>> _registeredNames = new(); public void RegisterOptionName<T>(string name) { var type = typeof(T); if (!_registeredNames.ContainsKey(type)) { _registeredNames[type] = new HashSet<string>(); } _registeredNames[type].Add(name ?? Options.DefaultName); } public bool IsOptionNameRegistered<T>(string? name) { var type = typeof(T); if (!_registeredNames.TryGetValue(type, out var names)) { return false; } return names.Contains(name ?? Options.DefaultName); } }
2. 扩展选项注册逻辑,自动记录名称
扩展IServiceCollection,让注册命名选项时自动将名称存入跟踪器:
public static class OptionsRegistrationExtensions { public static OptionsBuilder<TOptions> AddNamedOptions<TOptions>(this IServiceCollection services, string? name) where TOptions : class { services.AddSingleton<RegisteredOptionsTracker>(); var tracker = services.BuildServiceProvider().GetRequiredService<RegisteredOptionsTracker>(); tracker.RegisterOptionName<TOptions>(name); return services.AddOptions<TOptions>(name); } }
修改原有选项注册代码,使用这个扩展方法:
services.AddNamedOptions<GovernanceServiceOptions>(GovernanceServiceOptions.UnassignedApplications) .BindConfiguration(GovernanceServiceOptions.UnassignedApplications) .ValidateDataAnnotations() .ValidateOnStart();
3. 包装IOptionsSnapshot实现校验逻辑
创建包装类,拦截Get方法做合法性校验:
public class ValidatingOptionsSnapshot<TOptions> : IOptionsSnapshot<TOptions> where TOptions : class { private readonly IOptionsSnapshot<TOptions> _innerSnapshot; private readonly RegisteredOptionsTracker _tracker; public ValidatingOptionsSnapshot(IOptionsSnapshot<TOptions> innerSnapshot, RegisteredOptionsTracker tracker) { _innerSnapshot = innerSnapshot; _tracker = tracker; } public TOptions Value => _innerSnapshot.Value; public TOptions Get(string? name) { var targetName = name ?? Options.DefaultName; if (!_tracker.IsOptionNameRegistered<TOptions>(name)) { throw new InvalidOperationException($"命名选项 '{targetName}' 未注册,请检查选项名称是否正确。"); } return _innerSnapshot.Get(name); } }
4. 替换DI容器中的IOptionsSnapshot
在服务注册中替换默认实现为我们的校验包装类:
public static class ValidatingOptionsExtensions { public static IServiceCollection AddValidatingOptionsSnapshot<TOptions>(this IServiceCollection services) where TOptions : class { services.AddScoped<IOptionsSnapshot<TOptions>>(sp => { var innerSnapshot = sp.GetRequiredService<IOptionsSnapshot<TOptions>>(); var tracker = sp.GetRequiredService<RegisteredOptionsTracker>(); return new ValidatingOptionsSnapshot<TOptions>(innerSnapshot, tracker); }); return services; } }
在Program.cs/Startup中添加注册:
services.AddValidatingOptionsSnapshot<GovernanceServiceOptions>();
效果验证
现在调用governanceServiceOptions.Get("无效名称")会直接抛出InvalidOperationException;传入已注册的名称则正常返回配置好的选项。
内容的提问来源于stack exchange,提问作者Shuzheng
相关产品推荐
相关产品推荐

