在 .NET 中编写更好的配置文件

作者:微信公众号:【架构师老卢】
10-13 18:24
117

以下文章介绍如何使用最佳实践、高级功能和实际示例在 .NET 8 中编写更好的配置文件,以优化应用程序的配置过程。

了解 .NET 8 中的配置

.NET 中的配置是将应用程序设置外部化的操作,以便它们变得更容易更改,而无需接触代码库。

这种关注点分离使系统更易于维护,但也支持多种环境和部署方案。

关键组件

  • **配置提供程序:**这些必须从各种来源读取配置值。
  • **配置绑定:**这为配置提供了到 C# 类的直接映射,从而支持强类型并减少运行时错误。
  • **重新加载配置:**更重要的是,对于动态应用程序,应该可以在不重新启动应用程序的情况下重新加载配置。

appsettings.json 的基本结构

下面是一个非常简单的示例,说明了此类 appsettings.json 文件的外观:

{  
  "AppSettings": {  
    "ApplicationName": "MyApp",  
    "Version": "1.0",  
    "Environment": "Development"  
  },  
  "Logging": {  
    "LogLevel": {  
      "Default": "Information",  
      "Microsoft": "Warning"  
    }  
  },  
  "ConnectionStrings": {  
    "DefaultConnection": "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;"  
  }  
}

如何写出更好的appsettings.json

改进您的不仅与其结构有关,还与利用 .NET 8 的高级功能有关。 appsettings.json

这是有关改进配置文件的快速操作指南。

1. 对设置进行逻辑分组

对设置进行逻辑分组,这有助于保持可读性和可维护性。将类似的设置存储在一个逻辑组中。使用嵌套 / 子组来传达依赖关系。

{  
  "DatabaseSettings": {  
    "SqlServer": {  
      "ConnectionString": "Server=sqlServerAddress;Database=sqlDatabase;User Id=sqlUser;Password=sqlPassword;"  
    },  
    "MongoDb": {  
      "ConnectionString": "mongodb://mongoServerAddress:27017",  
      "DatabaseName": "myMongoDb"  
    }  
  },  
  "ApiSettings": {  
    "BaseUrl": "https://api.example.com",  
    "TimeoutSeconds": 30  
  }  
}

2. 使用强类型配置

将配置设置绑定到 C# 类,提供类型安全和 IntelliSense。如果您的设置名称或类型有拼写错误,它还会提供编译时错误。

例: 声明与设置结构匹配的 C# 类

public class DatabaseSettings  
{  
    public SqlServerSettings SqlServer { get; set; }  
    public MongoDbSettings MongoDb { get; set; }  
}  
  
public class SqlServerSettings  
{  
    public string ConnectionString { get; set; }  
}  
  
public class MongoDbSettings  
{  
    public string ConnectionString { get; set; }  
    public string DatabaseName { get; set; }  
}

Program.cs 文件中的设置:

var builder = Host.CreateDefaultBuilder(args)  
    .ConfigureAppConfiguration((context, config) =>  
    {  
        config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);  
    })  
    .ConfigureServices((context, services) =>  
    {  
        services.Configure<DatabaseSettings>(context.Configuration.GetSection("DatabaseSettings"));  
    });

3. 应用分层配置

您可以将配置节嵌套在其他配置节中。这在复杂配置的情况下非常有用。

{  
  "Logging": {  
    "LogLevel": {  
      "Default": "Warning",  
      "System": "Error"  
    },  
    "Console": {  
      "IncludeScopes": true  
    }  
  }  
}

映射到 C# 类:

public class LoggingSettings  
{  
    public LogLevelSettings LogLevel { get; set; }  
    public ConsoleSettings Console { get; set; }  
}  
  
public class LogLevelSettings  
{  
    public string Default { get; set; }  
    public string System { get; set; }  
}  
  
public class ConsoleSettings  
{  
    public bool IncludeScopes { get; set; }  
}

4. 利用特定于环境的配置

如果您的应用程序打算在不同的环境(即 、 和 )下运行,请使用特定于环境的配置文件。DevelopmentStagingProduction

.NET 8 允许您拥有像 appsettings 这样的文件。Development.json 和 appsettings。Production.json,这些设置将覆盖特定环境的 appsettings.json 中的设置。

{  
  "Logging": {  
    "LogLevel": {  
      "Default": "Debug"  
    }  
  }  
}

在 Program.cs 中,配置特定于环境的设置:

var builder = Host.CreateDefaultBuilder(args)  
    .ConfigureAppConfiguration((context, config) =>  
    {  
        var env = context.HostingEnvironment;  
        config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)  
              .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);  
    });

5. 保护敏感数据

对于敏感数据,如 API 密钥或连接字符串,请勿在 appsettings.json 中提交纯文本。请改用 secret 管理器或环境变量。

例: 通过 .NET Secret Manager 工具存储敏感数据

dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;"  

并从应用程序访问密钥

var connectionString = Configuration.GetConnectionString("DefaultConnection");

6. 利用配置验证

配置验证可确保您的配置值在使用之前有效。该功能在应用程序生命周期的早期可用于检测错误。

**例:**设置验证规则

public class DatabaseSettings  
{  
    public SqlServerSettings SqlServer { get; set; }  
      
    public void Validate()  
    {  
        if (string.IsNullOrEmpty(SqlServer?.ConnectionString))  
        {  
            throw new ArgumentException("SQL Server connection string is required.");  
        }  
    }  
}

在应用程序启动时验证配置:

public class Program  
{  
    public static void Main(string[] args)  
    {  
        var host = Host.CreateDefaultBuilder(args)  
            .ConfigureAppConfiguration((context, config) =>  
            {  
                config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);  
            })  
            .ConfigureServices((context, services) =>  
            {  
                var configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>();  
                var databaseSettings = configuration.GetSection("DatabaseSettings").Get<DatabaseSettings>();  
                databaseSettings.Validate();  
                services.AddSingleton(databaseSettings);  
            })  
            .Build();  
  
        host.Run();  
    }  
}  

7. 利用配置重新加载进行运行时更新

如果应用程序必须在不重新启动的情况下更新配置,请利用 ASP.NET Core Configuration 提供的配置重新加载功能。这通常适用于频繁更改的设置。

**例:**配置 appsettings.json 以在更改时重新加载配置:

var builder = Host.CreateDefaultBuilder(args)  
    .ConfigureAppConfiguration((context, config) =>  
    {  
        config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);  
    });

这将确保对 appsettings.json 文件所做的更改会自动重新加载配置,并反映动态更改。

8. 实现自定义配置提供程序

在需要对如何检索或处理配置值进行更多控制的情况下,实施自定义配置提供程序会有所帮助。当需要与 .NET 不支持的外部系统或格式集成时,这可能很有用。

**例:**实现自定义配置提供程序

public class CustomConfigurationProvider : ConfigurationProvider  
{  
    public override void Load()  
    {  
        // Custom logic to load configuration data  
        Data.Add("CustomKey", "CustomValue");  
    }  
}  
  
public class CustomConfigurationSource : IConfigurationSource  
{  
    public IConfigurationProvider Build(IConfigurationBuilder builder)  
    {  
        return new CustomConfigurationProvider();  
    }  
}

在 Program.cs 文件中注册您的自定义提供程序:

var builder = Host.CreateDefaultBuilder(args)  
    .ConfigureAppConfiguration((context, config) =>  
    {  
        config.Add(new CustomConfigurationSource());  
    });

9. 将配置快照用于不可变设置

当您的配置设置是不可变的 — 这意味着它们在运行时不会更改时,您可能需要考虑使用配置的快照。这有助于有效地进行设置管理,以保持整个应用程序的一致性。

例: 配置您的服务以使用快照

public class MySettings  
{  
    public string MyValue { get; set; }  
}  
  
public static class ServiceCollectionExtensions  
{  
    public static IServiceCollection AddMySettings(this IServiceCollection services, IConfiguration configuration)  
    {  
        services.Configure\<MySettings>(configuration.GetSection("MySettings"));  
        services.AddSingleton(resolver => resolver.GetRequiredService\<IOptionsSnapshot\<MySettings>>().Value);  
        return services;  
    }  
}

10. 使用内置功能标志

您可以启用或禁用某些功能,而无需部署新代码。.NET 8 通过 Microsoft.FeatureManagement 等库进行功能管理。

**例:**安装 Feature Management 包

dotnet add package Microsoft.FeatureManagement

在 appsettings.json 中配置功能标志:

{  
  "FeatureManagement": {  
    "NewFeature": true  
  }  
}

在应用程序中使用功能标志:

public class MyService  
{  
    private readonly IFeatureManager _featureManager;  
  
    public MyService(IFeatureManager featureManager)  
    {  
        _featureManager = featureManager;  
    }  
  
    public async Task DoWorkAsync()  
    {  
        if (await _featureManager.IsEnabledAsync("NewFeature"))  
        {  
            // Feature is enabled  
        }  
        else  
        {  
            // Feature is not enabled  
        }  
    }  
}

11. 基于数据注释的配置验证

可以在配置类上使用 DataAnnotations。这样,您就可以在尝试使用配置值之前确保它们满足已知条件。

**例:**使用 DataAnnotations 定义配置类

public class ApiSettings  
{  
    [Required]  
    public string BaseUrl { get; set; }  
  
    [Range(1, 60)]  
    public int TimeoutSeconds { get; set; }  
}

在启动时设置配置时启用验证:

public class Program  
{  
    public static void Main(string[] args)  
    {  
        var host = Host.CreateDefaultBuilder(args)  
            .ConfigureAppConfiguration((context, config) =>  
            {  
                config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);  
            })  
            .ConfigureServices((context, services) =>  
            {  
                var configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>();  
                var apiSettings = configuration.GetSection("ApiSettings").Get<ApiSettings>();  
                var validationContext = new ValidationContext(apiSettings);  
                Validator.ValidateObject(apiSettings, validationContext, validateAllProperties: true);  
                services.AddSingleton(apiSettings);  
            })  
            .Build();  
  
        host.Run();  
    }  
}

12. 合并多个配置源

为了使配置更灵活、更完整,必须合并多个配置源。换句话说,设置由appsettings.json、环境变量和用户密钥组合而成。

**例:**合并 Program.cs 中的配置源

var builder = Host.CreateDefaultBuilder(args)  
    .ConfigureAppConfiguration((context, config) =>  
    {  
        config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)  
              .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)  
              .AddEnvironmentVariables()  
              .AddUserSecrets<Program>();  
    });

13. 文档配置设置

可以添加注释或维护外部文档。appsettings.json

例: 可以将注释添加到appsettings.json。JSON 本身不支持注释。

可以使用外部工具或通过预处理 JSON 文件来支持注释

{  
  "Logging": {  
    "LogLevel": {  
      "Default": "Information", // Default logging level  
      "Microsoft": "Warning"   // Logging level for Microsoft libraries  
    }  
  }  
}

14. 配置库

例如,Serilog.Settings.Configuration 可用于维护日志记录配置。

例: 添加库

dotnet add package Serilog.Settings.Configuration

在 appsettings.json 配置 Serilog:

{  
  "Serilog": {  
    "MinimumLevel": {  
      "Default": "Information",  
      "Override": {  
        "Microsoft": "Warning"  
      }  
    },  
    "WriteTo": [  
      {  
        "Name": "Console"  
      }  
    ]  
  }  
}

在应用程序中使用 Serilog

public static class Program  
{  
    public static void Main(string[] args)  
    {  
        Log.Logger = new LoggerConfiguration()  
            .ReadFrom.Configuration(Configuration)  
            .CreateLogger();  
  
        try  
        {  
            Log.Information("Starting up");  
            CreateHostBuilder(args).Build().Run();  
        }  
        catch (Exception ex)  
        {  
            Log.Fatal(ex, "Application start-up failed");  
            throw;  
        }  
        finally  
        {  
            Log.CloseAndFlush();  
        }  
    }  
}
相关留言评论
昵称:
邮箱:
阅读排行