Just for an easier configuration I created a helper class that scans the configuration object for nested configurations, then tries to find a corresponding class in the loaded assemblies and initialize it with the given configuration.
appsettings.json:
{ "MyState": { "SomeSimpleValue": "Hello World", "MyTimeSpan": "00:15:00" } }
MyStateOptions.cs
// Class has same name as in appsettings.json with Options suffix. public class MyStateOptions { // Properties must be deserializable from string // or a class with a default constructor that has // only properties that are deserializable from string. public string SomeSimpleValue { get; set; } public DateTime MyTimeSpan { get; set; } }
Startup.cs
public class Startup { public IConfigurationRoot Configuration { get; } public Startup(IHostingEnvironment env) { // Create configuration as you need it... var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile(...) .AddEnvironmentVariables(); // Save configuration in property to access it later. Configuration = builder.Build(); } public void ConfigureServices(IServiceCollection services) { // Register all your desired services... services.AddMvc(options => ...); // Call our helper method services.RegisterOptions(Configuration); } }
HelperClass.cs
public static class IServiceCollectionExtensions { public static void RegisterOptions( this IServiceCollection services, IConfiguration configuration) { // Create all options from the given configuration. var options = OptionsHelper.CreateOptions(configuration); foreach (var option in options) { // We get back Options<MyOptionsType> : IOptions<MyOptionsType> var interfaces = option.GetType().GetInterfaces(); foreach (var type in interfaces) { // Register options IServiceCollection services.AddSingleton(type, option); } } } }
OptionsHelper.cs
public static class OptionsHelper { public static IEnumerable<object> CreateOptions(IConfiguration configuration) { // Get all sections that are objects: var sections = configuration.GetChildren() .Where(section => section.GetChildren().Any()); foreach (var section in sections) { // Add "Options" suffix if not done. var name = section.Key.EndsWith("Options") ? section.Key : section.Key + "Options"; // Scan AppDomain for a matching type. var type = FirstOrDefaultMatchingType(name); if (type != null) { // Use ConfigurationBinder to create an instance with the given data. var settings = section.Get(type); // Encapsulate instance in "Options<T>" var options = CreateOptionsFor(settings); } } } private static Type FirstOrDefaultMatchingType(string typeName) { // Find matching type that has a default constructor return AppDomain.CurrentDomain.GetAssemblies() .Where(assembly => !assembly.IsDynamic) .SelectMany(assembly => assembly.GetTypes()) .Where(type => type.Name == typeName) .Where(type => !type.IsAbstract) .Where(type => type.GetMatchingConstructor(Type.EmptyTypes) != null) .FirstOrDefault(); } private static object CreateOptionsFor(object settings) { // Call generic method Options.Create<TOptions>(TOptions options) var openGeneric = typeof(Options).GetMethod(nameof(Options.Create)); var method = openGeneric.MakeGenericMethod(settings.GetType()); return method.Invoke(null, new[] { settings }); } }
After doing all that stuff you can have a service within your service collection that demands in its constructor an IOptions<MyStateOptions> and you'll get it without explicitly configure each and every option you have. Just create a new project with the desired service and options instance. Add the project to your main project and add the desired configuration to your appsettings.json.
ExampleService.cs
public class MyExampleService { private readonly MyStateOptions _options; public MyExampleService(IOptions<MyStateOptions> options) { _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); } }