Skip to content

Instantly share code, notes, and snippets.

@egil
Last active January 12, 2023 15:22
Show Gist options
  • Save egil/0214d9fd7b0b9b08b51ad50d9a8d844f to your computer and use it in GitHub Desktop.
Save egil/0214d9fd7b0b9b08b51ad50d9a8d844f to your computer and use it in GitHub Desktop.
Helper methods for reading values from options types at runtime

Make getting option values easy

The asp.net configuration system allows you to pull in configuration settings from multiple places, have them bound at runtime to a type of your choosing, and injected into your services and types at runtime.

However, settings might not always be available, perhaps due to a misconfigration.

If we have the following options type:

public record class ExampleSetting(string? StringSetting, int? IntSetting);

We can use the helper methods defined in OptionsExtensions to get any setting that is required, or throw an MissingOptionsValueException exception like this:

public class MyService
{
  public MyService(IOptions<ExampleSetting> options)
  {
    string stringSetting = options.GetRequiredValue(x => x.StringSetting);
    int intSetting = options.GetRequiredValue(x => x.IntSetting);
  }
}
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
namespace Microsoft.Extensions.Options;
public static class OptionsExtensions
{
public static TValue GetRequiredValue<TOptions, TValue>(this IOptions<TOptions> options, Expression<Func<TOptions, TValue?>> valueSelector, [CallerArgumentExpression("options")] string? optionsName = null)
where TOptions : class
where TValue : notnull
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(valueSelector);
if (valueSelector.Compile().Invoke(options.Value) is not TValue result)
{
throw new MissingOptionsValueException(typeof(TOptions), GetPropertyInfo(valueSelector).Name, optionsName);
}
return result;
}
public static TValue GetRequiredValue<TOptions, TValue>(this IOptions<TOptions> options, Expression<Func<TOptions, TValue?>> valueSelector, [CallerArgumentExpression("options")] string? optionsName = null)
where TOptions : class
where TValue : struct
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(valueSelector);
if (valueSelector.Compile().Invoke(options.Value) is not TValue result)
{
throw new MissingOptionsValueException(typeof(TOptions), GetPropertyInfo(valueSelector).Name, optionsName);
}
return result;
}
private static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
{
if (propertyLambda.Body is not MemberExpression member)
{
throw new ArgumentException($"Expression '{propertyLambda.ToString()}' refers to a method, not a property.", nameof(propertyLambda));
}
if (member.Member is not PropertyInfo propInfo)
{
throw new ArgumentException($"Expression '{propertyLambda.ToString()}' refers to a field, not a property.", nameof(propertyLambda));
}
return propInfo;
}
}
public sealed class MissingOptionsValueException : ArgumentException
{
public Type OptionsType { get; }
public string PropertyName { get; }
public MissingOptionsValueException([NotNull] Type optionsType, string propertyName, string? paramName)
: base($"The options object {optionsType.Name} is missing a value for {propertyName}", paramName)
{
OptionsType = optionsType;
PropertyName = propertyName;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment