C# Academy · 课时

短路与分支

使用 MapWhen、UseWhen 和终止中间件,根据请求条件对管道进行分支或终止。

第 3 / 4 课12 个步骤

短路与分支 是 CoddyKit 上的免费 C# Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。

分支与短路

并非每个请求都应遍历完整的中间件管道。ASP.NET Core 提供了用于分支(将部分请求路由到不同管道)和短路(停止处理并立即返回)的工具。

Map:按路径永久分支

Map 会创建一个永久分支——匹配此前缀的请求会进入该分支,且绝不会返回主管道。

app.Map("/api", apiApp =>
{
    apiApp.UseAuthentication();
    apiApp.UseAuthorization();
    apiApp.Run(async ctx =>
        await ctx.Response.WriteAsync("API branch"));
});

// Requests to /api/... enter the branch above.
// Requests to /public/... skip the branch entirely.
app.Run(async ctx =>
    await ctx.Response.WriteAsync("Main pipeline"));

MapWhen:按谓词分支

MapWhen 可以根据任意条件进行分支(例如标头、查询参数或自定义逻辑)。与 Map 一样,该分支是永久的,不会重新加入主管道。

// Branch when a specific header is present
app.MapWhen(
    ctx => ctx.Request.Headers.ContainsKey("X-Internal"),
    internalApp =>
    {
        internalApp.UseMiddleware<InternalApiMiddleware>();
        internalApp.Run(async ctx =>
            await ctx.Response.WriteAsync("Internal route"));
    });

// Branch on query parameter
app.MapWhen(
    ctx => ctx.Request.Query.ContainsKey("legacy"),
    legacyApp => legacyApp.UseMiddleware<LegacyHandlerMiddleware>());

UseWhen:完成后重新加入的条件分支

UseWhen 类似于 MapWhen,但分支完成后会重新加入主管道。这样,您可以有条件地添加中间件,而无需创建独立分支。

// Log only for authenticated API calls; public routes not logged
app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/api")
           && ctx.User.Identity?.IsAuthenticated == true,
    loggedApp =>
    {
        loggedApp.UseMiddleware<AuditMiddleware>();
    });

// All requests continue here after the branch
app.UseRouting();
app.MapControllers();

不调用下一个组件来实现短路

任何中间件都可以通过写入响应而不调用 next 来实现短路。管道会在该处停止,响应也会返回给客户端。

app.Use(async (ctx, next) =>
{
    // IP allowlist check
    var ip = ctx.Connection.RemoteIpAddress?.ToString();
    var allowed = new[] { "127.0.0.1", "::1", "10.0.0.0/8" };

    if (!IsAllowed(ip, allowed))
    {
        ctx.Response.StatusCode = 403;
        await ctx.Response.WriteAsJsonAsync(
            new { error = "Access denied from this IP" });
        return; // short-circuit
    }

    await next(ctx); // proceed
});

.NET 8 中的 ShortCircuit()

在 .NET 8 中,MapShortCircuit 和端点上的 ShortCircuit() 扩展提供了一条声明式且高度优化的短路路径。

// Reject known bad paths immediately — skip entire pipeline
app.MapShortCircuit(404, "robots.txt", "favicon.ico");

// Or on a specific endpoint:
app.MapGet("/health", () => "OK")
   .ShortCircuit(); // skips auth, rate limiting, etc.

// 'Run' middleware also short-circuits:
app.Map("/old-api", old =>
    old.Run(ctx =>
    {
        ctx.Response.StatusCode = 301;
        ctx.Response.Headers.Location = "/api/v2";
        return Task.CompletedTask;
    }));

终止中间件与端点路由

在 .NET 3 之前,app.Run() 是唯一的终止选项。现在,更推荐使用端点路由(MapGet、MapControllers),因为它可以参与授权、速率限制和元数据处理。

// OLD: terminal middleware
app.Run(async ctx => {
    if (ctx.Request.Path == "/ping")
        await ctx.Response.WriteAsync("pong");
});

// PREFERRED: endpoint routing
app.MapGet("/ping", () => "pong")
   .WithTags("Health")
   .AllowAnonymous()
   .RequireRateLimiting("basic");

路径匹配细节

Map("/api") 会匹配 /api 和 /api/anything。在分支内部,匹配的路径会从 Request.Path 中移除,并放入 Request.PathBase。

app.Map("/api", apiApp =>
{
    apiApp.Use(async (ctx, next) =>
    {
        // Path in branch: /users/42 (prefix removed)
        Console.WriteLine(ctx.Request.Path);     // /users/42
        Console.WriteLine(ctx.Request.PathBase); // /api
        await next(ctx);
    });
    apiApp.MapGet("/users/{id}", (int id) => id);
});

组合使用分支模式

实际应用会组合使用 Map、UseWhen 和短路机制来创建分层管道:公共路由保持轻量,API 路由加入身份验证,管理路由则执行额外检查。

// Public area — no auth
app.Map("/public", pub =>
{
    pub.MapGet("/health", () => "OK");
    pub.MapGet("/docs",   () => "Documentation");
});

// API area — full auth + rate limiting
app.Map("/api", api =>
{
    api.UseAuthentication();
    api.UseAuthorization();
    api.UseRateLimiter();
    api.MapControllers();
});

// Admin area — require admin role
app.Map("/admin", admin =>
{
    admin.UseAuthentication();
    admin.UseAuthorization();
    admin.MapControllers().RequireAuthorization("AdminPolicy");
});

实际案例:A/B 测试中间件

A/B 测试中间件会根据 Cookie 或标头将请求分发给不同的处理程序,而无需修改端点代码。

app.UseWhen(
    ctx => ctx.Request.Cookies.TryGetValue("ab-group", out var g) && g == "B",
    betaApp =>
    {
        betaApp.Use(async (ctx, next) =>
        {
            ctx.Items["IsGroupB"] = true;
            await next(ctx);
        });
    });

// Endpoint reads the flag:
app.MapGet("/products", (HttpContext ctx, ProductService svc) =>
{
    bool isB = ctx.Items.ContainsKey("IsGroupB");
    return isB ? svc.GetNewLayoutAsync() : svc.GetOldLayoutAsync();
});

快速检查

MapWhen 和 UseWhen 的关键区别是什么?

回顾:短路与分支

关键要点:

  • Map:基于路径的永久分支(不会重新加入)
  • MapWhen:基于谓词的永久分支
  • UseWhen:完成后重新加入主管道的条件分支
  • 短路:不调用 next,写入响应并返回
  • ShortCircuit()(.NET 8):声明式、经过优化的终止端点
  • 组合使用分支来构建分层管道:公共 / API / 管理
免费开始

用 AI 导师学习 C# — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
93
课程
346

常见问题解答

「短路与分支」课时是免费的吗?

是的 — 「短路与分支」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。

「短路与分支」这节课中我会学到什么?

使用 MapWhen、UseWhen 和终止中间件,根据请求条件对管道进行分支或终止。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C# Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「短路与分支」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C# Academy 课中编写并运行代码吗?

能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. ASP.NET Core 管道概览
  2. 编写自定义中间件
  3. 短路与分支
  4. 中间件顺序与内置中间件
← 返回 C# Academy