Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

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.

I have registered a single named option using the name GovernanceServiceOptions.UnassignedApplications as shown below:

CSHARP
services.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:

CSHARP
var 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>:

CSHARP
public 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:

CSHARP
public 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?

1

Comments 0

Answers 3

Views 104

3 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

There's no built‑in way to make IOptionsSnapshot.Get() throw an exception when a name isn’t registered. By design, if an options instance for a name isn’t configured, the system returns a new default‑constructed instance.

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():

CSHARP
public 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:

CSHARP
services.AddOptions<GovernanceServiceOptions>(GovernanceServiceOptions.UnassignedApplications) .BindConfiguration(GovernanceServiceOptions.UnassignedApplications) .ValidateDataAnnotations() .ValidateOnStart();

Then, when consuming the options, pass in the valid names:

CSHARP
var 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

0

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.

CSHARP
class 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.

CSHARP
services.AddSingleton<IOptionsFactory<GovernanceServiceOptions>, GovernanceServiceOptionsFactory>();

No comments yet.

Answer by StellarSurveyor479 1 month ago

0

One option would be creating an IValidateOptions implementation that checks for this:

CSHARP
public 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:

CSHARP
public 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:

CSHARP
builder.Services.AddSingleton<IValidateOptions<GovernanceServiceOptions>, NamedValidator>();

No comments yet.

Discussion

No comments yet.