Asked 1 month ago by NovaCaptain965
How can I force IOptionsSnapshot<T>.Get() to throw an exception for unregistered option names?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by NovaCaptain965
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I have registered a single named option using the name GovernanceServiceOptions.UnassignedApplications
as shown below:
CSHARPservices.AddOptions<GovernanceServiceOptions>(GovernanceServiceOptions.UnassignedApplications) .BindConfiguration(GovernanceServiceOptions.UnassignedApplications) .ValidateDataAnnotations() .ValidateOnStart();
However, when I call IOptionsSnapshot<T>.Get()
with an invalid (unregistered) name, it returns a default instance (e.g. new GovernanceServiceOptions()
) instead of throwing an exception. My usage looks like this:
CSHARPvar options = governanceServiceOptions.Get(GovernanceServiceOptions.UnassignedApplications);
where governanceServiceOptions
is of type IOptionsSnapshot<GovernanceServiceOptions>
. I would like to protect the application against invalid option names by having an exception thrown when an unregistered name is provided.
Below is the definition for IOptionsSnapshot<T>
:
CSHARPpublic interface IOptionsSnapshot<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] out TOptions> : IOptions<TOptions> where TOptions : class { /// <summary> /// Returns a configured <typeparamref name="TOptions" /> instance with the given <paramref name="name" />. /// </summary> /// <param name="name">The name of the <typeparamref name="TOptions" /> instance, if <see langword="null" /> <see cref="F:Microsoft.Extensions.Options.Options.DefaultName" /> is used.</param> /// <returns>The <typeparamref name="TOptions" /> instance that matches the given <paramref name="name" />.</returns> TOptions Get(string? name); }
And here is the GovernanceServiceOptions
definition:
CSHARPpublic sealed record GovernanceServiceOptions { public static string UnassignedApplications => "GovernanceService:UnassignedApplications"; public static string StaleApplications => "GovernanceService:StaleApplications"; public static string StaleOrphanedApplications => "GovernanceService:StaleOrphanedApplications"; public bool Enabled { get; init; } = true; public int NotificationIntervalInDays { get; init; } = 30; public DryRunOptions DryRun { get; init; } = new(); public ProtectionOptions Protection { get; } = new(); } public record DryRunOptions { public bool Enabled { get; init; } public int? Percentage { get; init; } public int? MaxCount { get; init; } public List<string> IncludeOwners { get; init; } = []; } public record ProtectionOptions { public int BatchSize { get; init; } = 20; }
How can I modify or extend this behavior so that calling IOptionsSnapshot<T>.Get()
with an invalid name results in an exception rather than a default instance?
Comments 0
•Answers 3
•Views 104
Answer by TychoAI Agent • 1 month ago
There's no built‑in way to make IOptionsSnapshot
To handle invalid names, you need to add your own check. For example, you can maintain a set of valid option names and then create an extension (or wrapper) that validates the name before calling Get():
CSHARPpublic static class OptionsSnapshotExtensions { public static T GetValidated<T>( this IOptionsSnapshot<T> options, string name, IEnumerable<string> validNames) where T : class, new() { if (!validNames.Contains(name)) { throw new ArgumentException($"The options name '{name}' is not registered.", nameof(name)); } return options.Get(name); } }
You would register your option as usual:
CSHARPservices.AddOptions<GovernanceServiceOptions>(GovernanceServiceOptions.UnassignedApplications) .BindConfiguration(GovernanceServiceOptions.UnassignedApplications) .ValidateDataAnnotations() .ValidateOnStart();
Then, when consuming the options, pass in the valid names:
CSHARPvar validNames = new[] { GovernanceServiceOptions.UnassignedApplications, // Include other valid names if needed: // GovernanceServiceOptions.StaleApplications, // GovernanceServiceOptions.StaleOrphanedApplications }; var options = governanceServiceOptions.GetValidated(GovernanceServiceOptions.UnassignedApplications, validNames);
This way, if an invalid name is provided, your extension method will throw an exception before retrieving a default instance.
Since the default behavior is to return a default‑constructed instance rather than throw, protecting your application against invalid names requires such custom validation.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by MartianRanger308 • 1 month ago
You can control whether (and how) a GovernanceServiceOptions
instance should be created by implementing a custom IOptionsFactory<TOptions>
. It's easy to inherit from the default OptionsFactory<TOptions>
class having all the boilerplate code in place to handle the real instantiation.
When a GovernanceServiceOptions
is being requested with an unknown name, an instantiation can be avoided by e.g. throwing an exception (or returning null) from the CreateInstance
method.
The base implementation has a setups
constructor argument containing the names of the registered instances, upon which you can check whether the instantiation should continue or fail.
CSHARPclass GovernanceServiceOptionsFactory : OptionsFactory<GovernanceServiceOptions> { private readonly HashSet<string> _names; public GovernanceServiceOptionsFactory( IEnumerable<IConfigureOptions<GovernanceServiceOptions>> setups, IEnumerable<IPostConfigureOptions<GovernanceServiceOptions>> postConfigures, IEnumerable<IValidateOptions<GovernanceServiceOptions>> validates ) : base(setups, postConfigures, validates) => _names = setups.OfType<ConfigureNamedOptions<GovernanceServiceOptions, IConfiguration>>() .Select(o => o.Name!) .ToHashSet(); protected override GovernanceServiceOptions CreateInstance(string name) => _names.Contains(name) ? base.CreateInstance(name) : throw new InvalidOperationException($"Unknown name '{name}'"); }
In order to use this custom options factory, you need to have below DI registration in place.
CSHARPservices.AddSingleton<IOptionsFactory<GovernanceServiceOptions>, GovernanceServiceOptionsFactory>();
No comments yet.
Answer by StellarSurveyor479 • 1 month ago
One option would be creating an IValidateOptions
implementation that checks for this:
CSHARPpublic class NamedValidator : IValidateOptions<GovernanceServiceOptions> { public ValidateOptionsResult Validate(string name, GovernanceServiceOptions options) { if (name != "foo") { return ValidateOptionsResult.Fail("Name must be foo"); } return ValidateOptionsResult.Success; } }
a more generic solution that relies on some implementation details of the source code would be:
CSHARPpublic class NamedValidator : IValidateOptions<GovernanceServiceOptions> { List<string> _registeredNames = new(); public NamedValidator(IEnumerable<IConfigureOptions<GovernanceServiceOptions>> configuredOptions) { foreach (var item in configuredOptions) { if (item is IConfigureNamedOptions<GovernanceServiceOptions> named) { // TODO null check etc var nameProperty = named.GetType().GetProperty("Name"); var value = (string)nameProperty.GetValue(named); _registeredNames.Add(value); } } } public ValidateOptionsResult Validate(string name, GovernanceServiceOptions options) { if (_registeredNames.Contains(name) == false) { return ValidateOptionsResult.Fail("Options only accessed through registered name"); } return ValidateOptionsResult.Success; } }
And register:
CSHARPbuilder.Services.AddSingleton<IValidateOptions<GovernanceServiceOptions>, NamedValidator>();
No comments yet.
No comments yet.