将配置绑定到 .NET Core 2.0 中的对象图

Binding a configuration to an object graph in .NET Core 2.0

我正在制作 .NET Core 2.0 应用程序,我需要对其进行配置。我正在查看 this documentation,似乎在 .NET Core 1.0 中你可以这样做:

var appConfig = new AppSettings();
config.GetSection("App").Bind(appConfig);

在 .NET Core 1.1 中你可以这样做:

var appConfig = config.GetSection("App").Get<AppSettings>();

但 .NET Core 2.0 中不存在 Bind 和 Get。实现这一目标的新方法是什么?

谢谢,

乔什

如果您想在 Startup 期间注册配置,请将此添加到 Startup.cs:

services.Configure<AppSettings>(Configuration.GetSection("App"));

然后您可以通过注入 IOptions<>:

的实例来访问它
private readonly AppSettings _appSettings;
public MyClass(IOptions<AppSettings> appSettings) {
    _appSettings = appSettings.Value;
}

为了更简单的配置,我创建了一个帮助程序 class,它扫描配置对象的嵌套配置,然后尝试在加载的程序集中找到相应的 class 并使用给定的配置对其进行初始化.

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));
    }
}

您仍然可以同时执行这两项操作。由于您在控制台应用程序中,因此可能没有使用 ASP.NET 核心元数据包,因此您需要确保具有正确的依赖项。

为了将配置绑定到对象,您需要 Microsoft.Extensions.Configuration.Binder 包。那么,这两种解决方案应该都能正常工作。


顺便说一句。即使您在控制台应用程序中,您仍然可以使用 ASP.NET Core 附带的依赖注入容器。我个人发现它的设置非常简单,所以如果你仍然可以修改你的应用程序来使用它,那可能是值得的。设置看起来像这样:

var configuration = new ConfigurationBuilder()
    .AddJsonFile("config.json", optional: false)
    .Build();

var services = new ServiceCollection();
services.AddOptions();

// add your services here
services.AddTransient<MyService>();
services.AddTransient<Program>();

// configure options
services.Configure<AppSettings>(configuration.GetSection("App"));

// build service provider
var serviceProvider = services.BuildServiceProvider();

// retrieve main application instance and run the program
var program = serviceProvider.GetService<Program>();
program.Run();

然后,您所有已注册的服务都可以像在 ASP.NET Core 中那样获取依赖项。为了使用您的配置,您可以像往常一样注入 IOptions<AppSettings> 类型。

直到今天我终于弄明白之前,我仍然对此有疑问。

代码 运行 没有问题,但所有属性仍然为 null,即使在绑定之后也是如此。我是这样做的:

public class AppSettings
{
    public string MyProperty
}

事实证明你必须这样做:

public class AppSettings
{
    public string MyProperty { get; set; }
}

它仅在您的 class 具有属性而非字段时才有效。我不清楚。

这就是我绑定设置对象并将它们添加为 .Net Core 3.0 中的单例的方式

public void ConfigureServices(IServiceCollection services)
        {
            var jwtSettings = new JwtSettings();
            Configuration.Bind(jwtSettings);
            services.AddSingleton(jwtSettings);

            var databaseSettings = new DatabaseSettings();
            Configuration.Bind(databaseSettings);
            services.AddSingleton(databaseSettings);


            services.AddControllersWithViews();
        }

我的设置对象如下所示:

public class DatabaseSettings
    {
        public string ConnectionString { get; set; }
        public string DatabaseName { get; set; }
    }

public class JwtSettings
    {
        public string Secret { get; set; }
        public string Lifetime { get; set; }
    }

我的 appsettings.json 文件如下所示:

{
  "DatabaseSettings": {
    "ConnectionString": "mongodb://localhost:27017",
    "DatabaseName": "TestDb"
  },
  "JwtSettings": {
    "Secret": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "Lifetime": "170"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}