3、ASP.NET Core基础:创建最小API项目、理解Program.cs和启动配置、中间件管道概念

3.1 从零开始:创建最小API项目

说实话,我刚接触ASP.NET Core那会儿,也被各种模板搞晕过。MVC、Web API、Razor Pages……选哪个?

但现在不一样了。.NET 6之后,微软推出了「最小API」。说白了,就是让你用最少的代码,跑起一个Web服务。

我个人习惯用命令行创建项目,干净利落:

dotnet new web -n MyMinimalApi
cd MyMinimalApi
dotnet run

就这么三行,一个Web服务就跑起来了。你打开浏览器,访问 http://localhost:5000,就能看到 "Hello World!"。

嗯,这里要注意。这个模板生成的项目,只有一个文件——Program.cs。所有东西都塞在里面。我刚开始也觉得别扭,但用久了发现,对于小服务、微服务来说,这种方式反而清爽。

我的小技巧: 如果你做的是API网关或者BFF(Backend For Frontend),最小API是绝配。代码少,部署快,维护成本低。

3.2 解剖Program.cs:启动配置的秘密

打开Program.cs,你会看到这样的代码:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

这四行代码,背后藏着整个ASP.NET Core的启动机制。我拆开来讲。

3.2.1 builder:服务的容器

WebApplication.CreateBuilder(args) 创建了一个构建器。这个构建器,说白了就是你的「服务容器」。

你在里面注册什么服务,你的应用就能用什么服务。比如:

var builder = WebApplication.CreateBuilder(args);

// 注册数据库上下文
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));

// 注册自定义服务
builder.Services.AddScoped<IUserService, UserService>();

我曾经在一个项目中,忘了注册服务就直接在API里注入,结果运行时直接报错。嗯,那场面挺尴尬的。所以我的习惯是:先注册,后使用

3.2.2 app:请求管道的起点

var app = builder.Build(); 这行代码,构建了应用实例。这个app对象,就是整个请求管道的起点。

你想想看,一个HTTP请求进来,经过一系列处理,最后返回响应。这一系列处理步骤,就是「中间件管道」。

核心概念: builder负责「配置」,app负责「执行」。配置阶段注册服务和中间件,执行阶段处理请求和响应。

3.3 中间件管道:请求的流水线

中间件管道,你可以把它想象成一条流水线。每个中间件就是一个工位,负责处理请求的某个方面。

比如:

  • 第一个工位:异常处理(捕获未处理的异常)
  • 第二个工位:身份验证(检查用户是否登录)
  • 第三个工位:静态文件(返回CSS、JS等静态资源)
  • 第四个工位:路由(匹配URL,找到对应的处理逻辑)

每个中间件都可以决定:是继续往下传,还是直接返回响应。

3.3.1 中间件的注册顺序

这个顺序很重要。我踩过坑。

比如,你把异常处理中间件放在最后,那前面的中间件抛异常,就没人管了。请求直接挂掉,返回500错误,连个友好的提示都没有。

正确的顺序应该是:

var app = builder.Build();

// 1. 异常处理(放在最前面)
app.UseExceptionHandler("/error");

// 2. 身份验证
app.UseAuthentication();
app.UseAuthorization();

// 3. 静态文件
app.UseStaticFiles();

// 4. 路由
app.MapGet("/api/users", () => ...);

app.Run();

我曾经在一个生产项目中,把UseAuthentication放在了UseStaticFiles后面。结果呢?用户可以直接访问静态文件,绕过身份验证。嗯,那是个安全漏洞,还好在测试阶段发现了。

避坑指南: 中间件的注册顺序,决定了请求的处理顺序。异常处理永远在最前面,身份验证在路由之前,静态文件在动态内容之前。

3.3.2 自定义中间件

有时候,你需要自己写中间件。比如,记录每个请求的处理时间:

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    
    await next(); // 调用下一个中间件
    
    stopwatch.Stop();
    Console.WriteLine($"请求 {context.Request.Path} 耗时 {stopwatch.ElapsedMilliseconds} ms");
});

这个中间件,在请求进来时启动计时器,等后续中间件处理完,再记录耗时。简单吧?

我个人习惯把自定义中间件封装成扩展方法,方便复用:

public static class RequestTimingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder app)
    {
        return app.Use(async (context, next) =>
        {
            var stopwatch = Stopwatch.StartNew();
            await next();
            stopwatch.Stop();
            Console.WriteLine($"请求 {context.Request.Path} 耗时 {stopwatch.ElapsedMilliseconds} ms");
        });
    }
}

// 使用
app.UseRequestTiming();

3.4 启动配置:环境与配置

ASP.NET Core的启动配置,支持多环境。开发、测试、生产,各有各的配置。

默认情况下,CreateBuilder会加载:

  • appsettings.json(通用配置)
  • appsettings.{Environment}.json(环境特定配置)
  • 环境变量
  • 命令行参数

配置的优先级是:命令行 > 环境变量 > 环境特定JSON > 通用JSON。

举个例子:

// appsettings.json
{
  "Database": {
    "ConnectionString": "Server=localhost;Database=MyDb;..."
  }
}

// appsettings.Development.json
{
  "Database": {
    "ConnectionString": "Server=localhost;Database=MyDb_Dev;..."
  }
}

在开发环境,Development配置会覆盖通用配置。这样,你就不用担心误连生产数据库了。

我的习惯: 敏感信息(如数据库密码、API密钥)不要写在JSON文件里。用环境变量或者Azure Key Vault。我曾经见过有人把生产密码写在配置文件里,然后不小心提交到Git仓库……嗯,那是个灾难。

3.5 小结:从最小API到生产就绪

最小API虽然「最小」,但五脏俱全。它包含了ASP.NET Core的核心机制:

  • Builder:服务注册和配置加载
  • App:中间件管道和请求处理
  • 中间件:请求的流水线处理
  • 配置:多环境支持

你想想看,从三行代码开始,到支持身份验证、异常处理、静态文件、自定义中间件……这个演进过程,其实就是从「最小API」到「生产就绪」的过程。

下一章,我们会深入中间件管道,看看如何构建更复杂的请求处理流程。到时候,我会分享一些我在微服务架构中积累的中间件设计经验。

嗯,今天就到这里。动手试试吧,从dotnet new web开始,感受一下最小API的魅力。