【问题标题】:Binding a configuration to an object graph in .NET Core 2.0在 .NET Core 2.0 中将配置绑定到对象图
【发布时间】:2019-12-30 23:58:05
【问题描述】:

我正在制作一个 .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。实现这一目标的新方法是什么?

谢谢,

乔什

【问题讨论】:

    标签: c# .net-core-2.0


    【解决方案1】:

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

    为了将配置绑定到对象,您需要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&lt;AppSettings&gt; 类型。

    【讨论】:

    • 这主要解决了我的问题,但我仍然遇到一个问题,即所有值都保留为空。我的回答解释了我是如何解决这个问题的。
    【解决方案2】:

    直到今天我终于弄清楚了,我仍然有这个问题。

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

    public class AppSettings
    {
        public string MyProperty
    }
    

    事实证明你必须这样做:

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

    只有当你的类有属性而不是字段时它才有效。我不清楚。

    【讨论】:

    • 感谢 Heaps !!,花了几个小时试图解决这个确切的问题,值始终返回为 null,检查器始终将“configuration.getsection”值显示为 null。关于空值已经发布了很多问题;打赌很多人都有同样的问题
    • 谢谢!此外,具有公共设置器的属性(必需)。
    • 附带说明,有公共字段是没有意义的。您的字段不应该公开,否则只需使用属性
    【解决方案3】:

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

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

    然后您可以通过注入 IOptions&lt;&gt; 的实例来访问它:

    private readonly AppSettings _appSettings;
    public MyClass(IOptions<AppSettings> appSettings) {
        _appSettings = appSettings.Value;
    }
    

    【讨论】:

    • 这不只适用于 Asp.NET/MVC 吗?我只是在制作一个控制台应用程序。
    • 哦,我的错。它适用于 ASP.NET,但它也适用于控制台应用程序,但您必须添加包。
    【解决方案4】:

    这就是我绑定设置对象并将它们作为单例添加到 .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": "*"
    }
    

    【讨论】:

    • 我不知道为什么我必须指定部分名称,但是一旦我告诉它要检索哪个部分,这最终对我有用,如下所示: Configuration.GetSection("JwtSettings").Bind( jwtSettings);
    【解决方案5】:

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

    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&lt;MyStateOptions&gt;,并且您无需显式配置您拥有的每个选项即可获得它。只需使用所需的服务和选项实例创建一个新项目。将项目添加到您的主项目中,并将所需的配置添加到您的 appsettings.json。

    ExampleService.cs

    public class MyExampleService
    {
        private readonly MyStateOptions _options;
    
        public MyExampleService(IOptions<MyStateOptions> options)
        {
            _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
        }
    }
    

    【讨论】:

    • OptionsHelper.CreateOptions 不会返回任何东西
    • 你尝试了什么,什么没用。您是否使用调试器逐步完成了它。你在哪里没有得到任何东西?一种可能是FirstOrDefaultMatchingType() 没有找到匹配的类型,导致未加载包含目标类型的程序集。因此,更多信息会有所帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-13
    • 1970-01-01
    • 2018-09-16
    • 2021-10-06
    • 2019-11-11
    • 1970-01-01
    相关资源
    最近更新 更多