为了更简单的配置,我创建了一个帮助类来扫描配置对象的嵌套配置,然后尝试在加载的程序集中找到相应的类并使用给定的配置对其进行初始化。
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 });
}
}
完成所有这些工作后,您可以在服务集合中拥有一个服务,该服务在其构造函数中需要IOptions<MyStateOptions>,并且您无需显式配置您拥有的每个选项即可获得它。只需使用所需的服务和选项实例创建一个新项目。将项目添加到您的主项目中,并将所需的配置添加到您的 appsettings.json。
ExampleService.cs
public class MyExampleService
{
private readonly MyStateOptions _options;
public MyExampleService(IOptions<MyStateOptions> options)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
}