ASP.NET Core Web 开发

从 Program 启动流程、Kestrel 服务器到配置系统与日志体系,系统拆解 ASP.NET Core Web 开发的关键环节,涵盖路由、静态文件、会话与生产部署的完整实践路径。

1. 项目结构与启动流程

一句话总结: ASP.NET Core 应用从 Program.cs 的 WebApplication 构建器开始,Host 负责组装配置、日志、服务容器与中间件管线,是一切 Web 功能的宿主。

现代 ASP.NET Core 采用「最小宿主模型」:WebApplication.CreateBuilder 构建宿主,app 对象同时代表配置、服务容器和中间件管线。理解启动流程,等于拿到整个框架的地图。

// Program.cs 最小宿主
var builder = WebApplication.CreateBuilder(args);

// 1. 注册服务(依赖注入容器)
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// 2. 组装中间件管线(顺序即执行顺序)
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

// 3. 启动监听
app.Run();
环节职责
CreateBuilder加载配置、日志、DI 容器骨架
AddControllers注册 MVC/API 服务
Build固化配置与服务容器
UseXxx往管线里加中间件
MapControllers把控制器挂进路由表
Run启动 Kestrel 开始监听

一句话: 启动流程可以概括为「注册服务 + 组装管线」。services 管「能注入什么」,中间件管「请求怎么流」,两者分开理解,后续所有 Web 特性都能对号入座。

2. Kestrel 服务器

一句话总结: Kestrel 是 ASP.NET Core 内置的跨平台 Web 服务器,直接承载 HTTP 请求处理,配置与性能调优都围绕它展开。

Kestrel 是运行在进程内的 HTTP 服务器,替代了传统 IIS 的监听角色。它支持 HTTP/1.1、HTTP/2、HTTPS、Unix socket,还能和 IIS、Nginx 组成反向代理架构。

{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://*:8080",
        "Protocols": "Http1AndHttp2"
      },
      "Https": {
        "Url": "https://*:8443",
        "Certificate": {
          "Path": "/etc/certs/myapp.pfx",
          "Password": "secret"
        }
      }
    }
  }
}
Kestrel 配置说明
Endpoints.Url监听地址与端口
ProtocolsHTTP/1.1、HTTP/2
MaxRequestBodySize请求体上限
AllowSynchronousIO是否允许同步 I/O
Limits.KeepAliveTimeout保活超时

避坑: 生产环境常见架构是 Nginx / IIS 反代 → Kestrel。Kestrel 默认不处理 X-Forwarded-* 头,启用 HTTPS 重定向与真实 IP 记录时,必须调用 ForwardedHeaders 中间件,否则拿到的一律是代理地址。

3. 配置系统

一句话总结: 配置是「多源合并 + 环境覆盖」的体系,JSON、环境变量、命令行按优先级叠加,Options 模式把配置强类型化注入业务代码。

ASP.NET Core 配置由 IConfiguration 统一读取,数据来自多个 Provider,后面的覆盖前面的。默认优先级:命令行 > 环境变量 > appsettings.{Environment}.json > appsettings.json。

// appsettings.json
{
  "ConnectionStrings": {
    "Default": "Server=db;Database=Shop;User Id=sa;Password=xxx"
  },
  "Jwt": {
    "Issuer": "plume",
    "ExpireHours": 24
  }
}

// Options 模式:强类型绑定
public class JwtOptions
{
    public string Issuer { get; set; }
    public int ExpireHours { get; set; }
}

builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
配置源优先级用途
appsettings.json低默认值
appsettings.{Env}.json中按环境覆盖
环境变量高部署注入密钥
命令行最高启动参数

避坑: 密钥(数据库密码、JWT 私钥)绝不能写进 appsettings.json 提交仓库。生产用环境变量或密钥管理服务注入,并在启动时校验缺失即快速失败。Options 模式让配置从「散落字符串」变成「可测试的强类型对象」。

4. 日志体系

一句话总结: 日志由 LoggerFactory + Provider 构成,用结构化和日志级别过滤生产噪音,ILogger 注入让业务代码与日志实现解耦。

ASP.NET Core 日志走 ILogger<T> 注入,Provider 决定日志去哪(控制台、文件、ES 等)。日志级别从 Trace 到 Critical,按需过滤;结构化日志把占位符变成字段,便于检索。

// 注入并使用结构化日志
public class WeatherService(ILogger<WeatherService> logger)
{
    public async Task<Weather> GetAsync(string city)
    {
        logger.LogInformation("查询天气 {City} 于 {Time}",
            city, DateTimeOffset.UtcNow);
        return await _api.GetAsync(city);
    }
}

// appsettings.json 中的级别过滤
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}
日志级别含义典型场景
Trace / Debug最详细本地调试
Information常规信息业务埋点
Warning异常但不致命重试、降级
Error已处理错误异常记录
Critical致命进程级故障

避坑: 不要用字符串拼接拼日志,LogInformation($"...{x}") 即使日志级别被过滤,拼接也会执行。用占位符模板,过滤掉时零开销。另外结构化日志的字段名用 {CamelCase} 模板,查询时才能稳定索引。

5. 路由与控制器

一句话总结: 路由把 URL 映射到处理端点,控制器或 Minimal API 都依赖路由表;属性路由、约束与绑定是 API 设计的核心语法。

路由决定「哪个 URL 进哪个 Action」。ASP.NET Core 支持控制器 + 属性路由,也支持 Minimal API 的函数式端点。请求数据通过模型绑定注入参数,返回值经格式化器序列化。

[ApiController]
[Route("api/orders")]
public class OrdersController(OrderService service) : ControllerBase
{
    [HttpGet("{id:int}")]
    public async Task<ActionResult<Order>> GetById(int id)
    {
        var order = await service.GetAsync(id);
        return order is null ? NotFound() : Ok(order);
    }

    [HttpPost]
    public async Task<ActionResult<Order>> Create(
        [FromBody] CreateOrderRequest request)
    {
        var created = await service.CreateAsync(request);
        return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
    }
}

// Minimal API 等价形式
app.MapGet("/api/orders/{id:int}", async (int id, OrderService svc) =>
{
    var order = await svc.GetAsync(id);
    return order is null ? Results.NotFound() : Results.Ok(order);
});
路由要素说明
[Route] / [HttpGet]控制器级与 Action 级路由
{id:int} 约束类型约束,匹配失败自动 404
[FromBody]JSON 请求体绑定
[FromQuery]查询字符串绑定
[ApiController]自动模型校验、自动 400

一句话: 路由与绑定决定了 API 的「外表」。约束、校验、REST 语义(CreatedAtAction、NotFound、Ok)统一后,接口契约会非常清晰,错误返回也不再散落各处。

6. 静态文件与会话

一句话总结: 静态文件中间件服务前端资源,会话与缓存中间件处理无状态 HTTP 的附加状态,按需开启并注意顺序。

Web 应用不只有 API,还有静态资源、会话、缓存、压缩等横切能力。它们都是中间件,顺序决定行为。

var app = builder.Build();

// 静态文件:wwwroot 下的资源
app.UseStaticFiles();

// 会话:需要先启用才能注入 IHttpContextAccessor / ISession
app.UseSession();

// 响应压缩:按 Accept-Encoding 压缩
app.UseResponseCompression();

// 请求限流:保护端点
app.UseRateLimiter();
中间件作用顺序要求
UseStaticFiles服务 wwwroot尽早
UseSession会话状态在路由前
UseResponseCompressiongzip/brotli靠前
UseRateLimiter限流在受保护端点前

避坑: 中间件顺序即执行顺序,写错会出隐蔽问题——例如静态文件中间件放路由之后,静态资源可能永远匹配不到;会话中间件放授权之后,控制器里读不到 Session。把通用中间件尽量前置,业务中间件靠后。

7. 部署与生产实践

一句话总结: 生产部署围绕环境切换、反向代理、健康检查与监控展开,进程外托管让 Nginx/IIS 与 Kestrel 各司其职。

生产环境的关键词是「托管、观测、韧性」:发布产物用 dotnet publish 输出自包含或框架依赖包;进程外托管由 Nginx/IIS 反代;健康检查端点暴露给负载均衡器;日志与指标接进集中平台。

# 发布为框架依赖,配合 Nginx 反代
dotnet publish -c Release -o /app/publish

# Nginx 反代片段
# location / {
#     proxy_pass http://127.0.0.1:8080;
#     proxy_set_header Host $host;
#     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# }
// 健康检查端点
builder.Services.AddHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/healthz");

// 生产开关:HTTPS 重定向 + 转发头
if (app.Environment.IsProduction())
{
    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor
                          | ForwardedHeaders.XForwardedProto
    });
}
生产实践要点
反向代理Nginx/IIS 前置,Kestrel 只监听内网
健康检查/healthz 暴露给 LB 探活
日志汇聚Serilog + 文件/ES 落盘
环境配置appsettings.Production.json 覆盖
发布dotnet publish -c Release

一句话: 部署的本质是「把开发环境的行为搬到生产环境的约束里」。用环境配置文件切换密钥、用反代终止 TLS、用健康检查做自动扩缩的依据,Web 服务才算真正上了生产。

8. 总结

环节要点
启动流程CreateBuilder + 注册服务 + 组装管线 + Run
Kestrel内置服务器,反代 + ForwardedHeaders
配置多源合并,Options 强类型注入
日志ILogger 注入 + 结构化模板 + 级别过滤
路由控制器/Minimal API + 约束 + 绑定
静态与会话中间件顺序即行为
部署publish + 反代 + 健康检查 + 日志汇聚

ASP.NET Core 把 Web 开发拆成清晰的层次:宿主、服务器、配置、日志、路由、中间件。这一篇把骨架立起来,依赖注入与中间件管线的深层机制在下一篇展开,两者合起来就是完整的请求处理世界观。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「csharp」更多文章

  1. 消息与后台任务
  2. 缓存与并发控制
  3. 测试体系:xUnit 与 Moq