.NET 配置、选项绑定与日志实践

系统讲解 ASP.NET Core 中的配置体系、选项绑定、环境隔离与日志实践,帮助建立稳定的工程基础。

.NET 配置、选项绑定与日志实践

很多 .NET 初学者写项目时,最容易忽视的不是控制器和数据库,而是配置和日志。可一旦项目开始走向多人协作、分环境部署和线上排障,这两部分就会立刻变成核心基础设施。

你可以把它们简单理解成:

  • 配置:让程序知道“该怎么运行”
  • 日志:让你知道“它刚刚做了什么、哪里出了问题”

如果这两块做得差,项目即使功能能跑,也很难维护。


第一章 ASP.NET Core 的配置体系在解决什么问题

一个真实项目通常会有很多可变化的信息:

  • 数据库连接串
  • JWT 密钥
  • Redis 地址
  • 第三方接口地址
  • 文件存储路径
  • 日志级别
  • 环境名称

这些内容不应该硬编码在代码里,而应该通过配置体系注入进来。


第二章 常见配置来源

ASP.NET Core 中,常见配置来源包括:

  • appsettings.json
  • appsettings.Development.json
  • 环境变量
  • 命令行参数
  • 用户机密或外部配置中心

2.1 一个基础配置文件示例

{
  "ConnectionStrings": {
    "Default": "Server=.;Database=DemoDb;Trusted_Connection=True;TrustServerCertificate=True;"
  },
  "Jwt": {
    "Issuer": "demo-api",
    "Audience": "demo-client",
    "Key": "your-secret-key"
  },
  "Storage": {
    "UploadPath": "uploads"
  }
}

2.2 环境配置文件

开发环境通常会额外有:

appsettings.Development.json

里面只放和开发环境不同的配置,例如:

{
  "Storage": {
    "UploadPath": "uploads-dev"
  }
}

第三章 配置优先级怎么理解

一般来说,配置来源存在覆盖关系。常见认知可以先这样记:

  • 默认文件提供基础配置
  • 环境文件覆盖环境差异
  • 环境变量可进一步覆盖
  • 命令行参数通常优先级更高

这意味着:

  • 本地开发可以用 appsettings.Development.json
  • 生产部署可以通过环境变量覆盖敏感值

这也是为什么“同一套代码部署到不同环境仍能工作”。


第四章 三种常见读取配置的方式

4.1 直接读取单个配置项

var issuer = builder.Configuration["Jwt:Issuer"];

这种方式简单,但不适合大量散落使用。

4.2 在服务里读取 IConfiguration

public class TokenService
{
    private readonly IConfiguration _configuration;

    public TokenService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public string GetIssuer()
    {
        return _configuration["Jwt:Issuer"] ?? string.Empty;
    }
}

这种方式在少量使用时没问题,但如果每个服务都到处读字符串路径,可维护性会变差。

4.3 选项绑定 Options Pattern

这是更推荐的做法。

定义配置类:

public class JwtOptions
{
    public string Issuer { get; set; } = string.Empty;
    public string Audience { get; set; } = string.Empty;
    public string Key { get; set; } = string.Empty;
}

注册:

builder.Services.Configure<JwtOptions>(
    builder.Configuration.GetSection("Jwt"));

使用:

using Microsoft.Extensions.Options;

public class TokenService
{
    private readonly JwtOptions _jwtOptions;

    public TokenService(IOptions<JwtOptions> options)
    {
        _jwtOptions = options.Value;
    }

    public string GetIssuer()
    {
        return _jwtOptions.Issuer;
    }
}

第五章 为什么推荐选项绑定

选项绑定的好处不仅仅是“写法更优雅”,更关键的是:

  • 配置结构更清晰
  • 类型安全更好
  • 便于集中维护
  • 更适合校验

尤其当配置项越来越多时,Options Pattern 会比到处写:

_configuration["Jwt:Issuer"]

更稳。


第六章 配置校验

很多线上问题,不是代码逻辑错了,而是配置缺了、配错了。

所以配置最好尽量在启动阶段就校验。

6.1 一个简单示例

using System.ComponentModel.DataAnnotations;

public class StorageOptions
{
    [Required]
    public string UploadPath { get; set; } = string.Empty;
}

注册时结合校验:

builder.Services
    .AddOptions<StorageOptions>()
    .Bind(builder.Configuration.GetSection("Storage"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

这样如果关键配置没填,程序会尽早在启动阶段暴露问题,而不是等运行到一半才炸。


第七章 配置敏感信息时要注意什么

7.1 不要把密钥写死在仓库里

例如下面这些都不应该直接提交到公共仓库:

  • 数据库密码
  • JWT Key
  • 对象存储密钥
  • 第三方平台 Access Key

7.2 生产环境优先用环境变量或密钥管理

常见做法:

  • 本地开发:配置文件
  • 测试和生产:环境变量、密钥服务、配置中心

7.3 连接串不要散落在多个文件里

如果配置位置混乱,排障会非常痛苦。


第八章 日志在项目中到底有什么用

很多初学者会把日志理解成“出错时打印一句”。实际上日志的作用远不止如此。

日志常用于:

  • 记录系统运行状态
  • 排查线上问题
  • 审计关键行为
  • 定位性能瓶颈
  • 还原请求链路

你可以把日志理解成系统的“运行痕迹”。


第九章 .NET 默认日志体系

ASP.NET Core 内置了统一日志抽象,最常见的是通过 ILogger<T> 使用。

9.1 基础示例

public class UserService
{
    private readonly ILogger<UserService> _logger;

    public UserService(ILogger<UserService> logger)
    {
        _logger = logger;
    }

    public void CreateUser(string userName)
    {
        _logger.LogInformation("开始创建用户: {UserName}", userName);
        _logger.LogInformation("用户创建完成: {UserName}", userName);
    }
}

9.2 为什么推荐结构化日志占位符

推荐:

_logger.LogInformation("创建订单成功, OrderId: {OrderId}", orderId);

不推荐:

_logger.LogInformation($"创建订单成功, OrderId: {orderId}");

前者更适合后续日志采集、检索和结构化分析。


第十章 日志级别怎么用

常见日志级别从低到高大致是:

  • Trace
  • Debug
  • Information
  • Warning
  • Error
  • Critical

10.1 常见使用建议

  • Information:记录正常业务流程的关键节点
  • Warning:记录可恢复但值得关注的问题
  • Error:记录操作失败或异常
  • Critical:记录严重系统故障

10.2 一个简单例子

_logger.LogInformation("开始处理登录请求");
_logger.LogWarning("用户多次密码输入错误: {UserName}", userName);
_logger.LogError(ex, "创建订单失败, OrderId: {OrderId}", orderId);

第十一章 如何配置日志级别

appsettings.json 中可以配置日志级别:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

这段配置的意义是:

  • 默认日志输出 Information 及以上
  • 框架内部大量噪声日志只保留到 Warning

这样能减少无效输出。


第十二章 业务项目里日志应该记什么,不该记什么

12.1 推荐记录

  • 请求开始和结束
  • 关键业务动作
  • 外部依赖调用失败
  • 核心参数和标识
  • 异常详情

12.2 不推荐记录

  • 明文密码
  • 完整身份证号、银行卡号等敏感信息
  • 过多无意义的重复日志

日志不是越多越好,而是要“有用且可检索”。


第十三章 一个简单的请求日志中间件示例

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(
        RequestDelegate next,
        ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation(
            "请求开始: {Method} {Path}",
            context.Request.Method,
            context.Request.Path);

        await _next(context);

        _logger.LogInformation(
            "请求结束: {StatusCode}",
            context.Response.StatusCode);
    }
}

注册:

app.UseMiddleware<RequestLoggingMiddleware>();

这类中间件非常适合建立最基础的请求链路日志。


第十四章 初学者常见坑

14.1 配置项到处直接字符串读取

时间一长,维护会变得很差,建议逐步收敛到选项绑定。

14.2 把生产密钥写进 appsettings.json

这会带来很大安全风险。

14.3 日志全都用 Information

这样日志就失去了分级意义。

14.4 异常只 Console.WriteLine

在 Web 项目和服务项目里,这远远不够。

14.5 日志里记录敏感信息

这是很多团队上线后才发现的问题,但代价通常很高。


第十五章 入门阶段建议掌握到什么程度

如果你刚开始学 .NET 工程基础,建议至少做到:

  1. 了解配置来源和环境覆盖关系
  2. 会用 appsettings.json 和环境配置文件
  3. 会用 Options Pattern 做选项绑定
  4. 知道如何做配置校验
  5. 会使用 ILogger<T> 记录结构化日志
  6. 知道日志级别如何划分

做到这一步后,你的 .NET 项目就已经具备了较好的工程基础。


延伸阅读

  • 如果你还没掌握 .NET 工程基础,先阅读 .NET CLI 与项目结构
  • 如果你想了解请求处理和中间件如何配合日志与配置,继续阅读 ASP.NET Core 基础
  • 如果你想把异常也纳入统一治理,继续阅读 ASP.NET Core 统一异常处理与统一返回

本文小结

配置和日志看似不像控制器、数据库那样“能立刻看到业务效果”,但它们是项目从“能跑”走向“能维护、能排障、能部署”的关键基础设施。

把这两块做好,你的 .NET 项目才真正有工程化的味道。