理解ASP.NET核心请求和反应管道----第三部分:中软件管道 (中文 (Chinese Simplified))

理解ASP.NET核心请求和反应管道----第三部分:中软件管道

Sunday, 09 November 2025

//

15 minute read

一. 导言 导言 导言 导言 导言 导言 一,导言 导言 导言 导言 导言 导言

我们探索了第二部分的主机层和 Kestrel 服务器。 现在我们到达了 ASP. NET Core 的核心: 中器件管道。 这就是您应用程序的逻辑, 处理请求, 以及建立响应的地方。 理解中器对于掌握 ASP. NET Core 至关重要 。

Middleware在概念上简单简单:每个构件都是一个处理 HTTP 请求的功能,它的力量足以处理从认证到错误处理到服务文件等所有事务。在这个部分,我们将探索中器如何运作,检查内置组件,并学会创建自己的组件。

注:这是一系列实验物品的一部分(因此是过去的东西),是人工智能和我自己编辑和调整的组合。

什么是Midleware? 什么是Midleware? 什么是Midleware? 什么是Midleware? 什么是Midleware?

Middleware是软件 集中成一个应用程序管道 处理请求和回应

  1. 选择 是否将请求传递给管道中的下一部分
  2. 能够从事工作 管道中下一部分之前和之后

把它想象成一系列嵌套函数呼叫, 每一个中间软件都包住下一个 :

graph LR
    A[Request] --> B[Middleware 1]
    B --> C[Middleware 2]
    C --> D[Middleware 3]
    D --> E[Endpoint]
    E --> D
    D --> C
    C --> B
    B --> F[Response]

每个中继器能:

  • 将请求传递给下一个中继器前先处理请求
  • 短路,立即回复
  • 在通过管道返回时处理反应过程

中件代表签名

以其核心,中间器械只是带有这个签名的一个功能:

public delegate Task RequestDelegate(HttpContext context);

每个中器接收到:

  • HttpContext context - 包含所有请求和答复信息
  • RequestDelegate next - 下一个中继器械在管道中

中件执行流程

让我们想象一下请求是如何通过中间软件流动的:

sequenceDiagram
    participant Client
    participant MW1 as Middleware 1
    participant MW2 as Middleware 2
    participant MW3 as Middleware 3
    participant Endpoint

    Client->>MW1: HTTP Request
    Note over MW1: Before logic
    MW1->>MW2: next()
    Note over MW2: Before logic
    MW2->>MW3: next()
    Note over MW3: Before logic
    MW3->>Endpoint: next()
    Note over Endpoint: Execute endpoint
    Endpoint-->>MW3: Return
    Note over MW3: After logic
    MW3-->>MW2: Return
    Note over MW2: After logic
    MW2-->>MW1: Return
    Note over MW1: After logic
    MW1-->>Client: HTTP Response

关键点 :

  • 请求通过管道(从上到下)不断流动
  • 通过管道(从下到上)向后逆流
  • 每个中继器件都可以在调用前和调用后执行代码 next()
  • Midroware 可以通过不打电话来短路短路 next()

创建中继器: 三种方法

1. 内嵌中中软件与 Use()

最简单的方法使用 ambda 表达式 :

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

app.Use(async (context, next) =>
{
    // Before the next middleware
    Console.WriteLine($"Request: {context.Request.Path}");
    var startTime = DateTime.UtcNow;

    // Call the next middleware
    await next(context);

    // After the next middleware
    var elapsed = DateTime.UtcNow - startTime;
    Console.WriteLine($"Response: {context.Response.StatusCode} ({elapsed.TotalMilliseconds}ms)");
});

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

app.Run();

访问时输出输出 /:

Request: /
Response: 200 (15.3ms)

2. 与 Run()

Run() 创建结束管道的中继器件 :

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

app.Use(async (context, next) =>
{
    Console.WriteLine("This executes");
    await next(context);
});

app.Run(async context =>
{
    Console.WriteLine("This is terminal - no next() to call");
    await context.Response.WriteAsync("End of pipeline");
});

// This never executes because Run() terminates the pipeline
app.Use(async (context, next) =>
{
    Console.WriteLine("This never executes");
    await next(context);
});

app.Run();

3. 基于类的中小软件

对于复杂的中件,使用类:

// Middleware class
public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestTimingMiddleware> _logger;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var sw = Stopwatch.StartNew();

        // Store start time in HttpContext.Items for other middleware to access
        context.Items["RequestStartTime"] = DateTime.UtcNow;

        try
        {
            await _next(context);
        }
        finally
        {
            sw.Stop();
            _logger.LogInformation(
                "Request {Method} {Path} completed in {ElapsedMs}ms with status {StatusCode}",
                context.Request.Method,
                context.Request.Path,
                sw.ElapsedMilliseconds,
                context.Response.StatusCode
            );
        }
    }
}

// Extension method for convenience
public static class RequestTimingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestTimingMiddleware>();
    }
}

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

app.UseRequestTiming();

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

app.Run();

产出:

info: RequestTimingMiddleware[0]
      Request GET / completed in 12ms with status 200

中软件秩序事项

您在管道中添加中间器件的顺序至关重要。 以下是推荐的顺序 :

graph TD
    A[Exception Handler] --> B[HSTS]
    B --> C[HTTPS Redirection]
    C --> D[Static Files]
    D --> E[Routing]
    E --> F[CORS]
    F --> G[Authentication]
    G --> H[Authorization]
    H --> I[Custom Middleware]
    I --> J[Session]
    J --> K[Response Caching]
    K --> L[Response Compression]
    L --> M[Endpoints]

为什么要这个命令?

  1. 例外处理器 - 必须首先抓住所有例外
  2. 顶顶 - 应提前确定的安全信头
  3. HTTPS 转导 - 处理前转至HTTPS
  4. 静静文件 - 静态内容能够提前短路
  5. 路路运行 - 将请求匹配到终点
  6. COR 库中 - 必须是在路过后和认证前
  7. 验证 - 确定用户是谁
  8. 授权授权 - 确定用户的能力
  9. 自定义 Midlesware 软件 - 你的应用逻辑
  10. 届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议届会议 - 州级管理
  11. 反应缓存 - 缓存响应
  12. 反应压缩 - 压缩反应
  13. 终点 - 执行匹配的终点

让我们在代码中看到这个:

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
builder.Services.AddResponseCaching();
builder.Services.AddResponseCompression();
builder.Services.AddSession();
builder.Services.AddCors();

var app = builder.Build();

// 1. Exception handling (must be first)
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/error");
    app.UseHsts(); // HTTP Strict Transport Security
}

// 2. HTTPS redirection
app.UseHttpsRedirection();

// 3. Static files (can short-circuit)
app.UseStaticFiles();

// 4. Routing (matches endpoints)
app.UseRouting();

// 5. CORS (after routing, before auth)
app.UseCors();

// 6. Authentication (who are you?)
app.UseAuthentication();

// 7. Authorization (what can you do?)
app.UseAuthorization();

// 8. Custom middleware
app.UseRequestTiming();

// 9. Session
app.UseSession();

// 10. Response caching
app.UseResponseCaching();

// 11. Response compression
app.UseResponseCompression();

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

app.Run();

内置中型深潜潜

处理器中件例外

捕捉后期中器件的例外, 产生错误反应 :

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

// Global exception handler
app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        context.Response.StatusCode = 500;
        context.Response.ContentType = "application/json";

        var error = context.Features.Get<IExceptionHandlerFeature>();
        if (error != null)
        {
            var ex = error.Error;

            await context.Response.WriteAsJsonAsync(new
            {
                error = new
                {
                    message = ex.Message,
                    type = ex.GetType().Name,
                    stackTrace = app.Environment.IsDevelopment() ? ex.StackTrace : null
                }
            });
        }
    });
});

// This will be caught by the exception handler
app.MapGet("/error", () =>
{
    throw new InvalidOperationException("Something went wrong!");
});

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

app.Run();

测试 :

$ curl http://localhost:5000/error
{
  "error": {
    "message": "Something went wrong!",
    "type": "InvalidOperationException",
    "stackTrace": "..."
  }
}

静静文件中件

提供静态文件和短路管道:

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

// Default: serves files from wwwroot/
app.UseStaticFiles();

// Serve files from additional directory
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
    RequestPath = "/StaticFiles"
});

// Custom file type (MIME mapping)
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".myapp"] = "application/x-myapp";

app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider
});

// Enable directory browsing (development only!)
if (app.Environment.IsDevelopment())
{
    app.UseDirectoryBrowser(new DirectoryBrowserOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(builder.Environment.ContentRootPath, "wwwroot")),
        RequestPath = "/browse"
    });
}

app.Run();

流程图 :

graph TD
    A[Request: /css/site.css] --> B{Static Files Middleware}
    B -->|File exists| C[Serve file]
    C --> D[Return 200]
    B -->|File not found| E[Call next middleware]
    E --> F[Routing/Endpoints]
    F --> G[Return 404]

    style C stroke:#10b981,stroke-width:3px
    style G stroke:#ef4444,stroke-width:3px

Midroup 身份验证中软件

根据请求认证用户 :

var builder = WebApplication.CreateBuilder(args);

// Add authentication services
builder.Services.AddAuthentication("Cookies")
    .AddCookie("Cookies", options =>
    {
        options.LoginPath = "/login";
        options.LogoutPath = "/logout";
        options.ExpireTimeSpan = TimeSpan.FromHours(1);
    })
    .AddJwtBearer("Bearer", options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "https://myapp.com",
            ValidAudience = "https://myapp.com",
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes("your-secret-key-here"))
        };
    });

var app = builder.Build();

app.UseAuthentication(); // Must be before UseAuthorization()

app.MapGet("/public", () => "Anyone can access this");

app.MapGet("/protected", () => "Only authenticated users can access this")
    .RequireAuthorization();

app.MapGet("/login", async (HttpContext context) =>
{
    var claims = new[]
    {
        new Claim(ClaimTypes.Name, "testuser"),
        new Claim(ClaimTypes.Email, "[email protected]")
    };

    var identity = new ClaimsIdentity(claims, "Cookies");
    var principal = new ClaimsPrincipal(identity);

    await context.SignInAsync("Cookies", principal);

    return Results.Ok("Logged in");
});

app.Run();

授权中心软件

确定经认证的用户是否允许访问资源:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication("Cookies")
    .AddCookie("Cookies");

// Configure authorization policies
builder.Services.AddAuthorization(options =>
{
    // Policy: Requires specific claim
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireClaim("role", "admin"));

    // Policy: Requires age over 18
    options.AddPolicy("Adults", policy =>
        policy.RequireAssertion(context =>
        {
            var ageClaim = context.User.FindFirst("age");
            if (ageClaim != null && int.TryParse(ageClaim.Value, out var age))
            {
                return age >= 18;
            }
            return false;
        }));

    // Policy: Combines requirements
    options.AddPolicy("AdminOrManager", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim("role", "admin") ||
            context.User.HasClaim("role", "manager")));
});

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// No authorization required
app.MapGet("/public", () => "Public endpoint");

// Requires authentication
app.MapGet("/authenticated", () => "Authenticated endpoint")
    .RequireAuthorization();

// Requires specific policy
app.MapGet("/admin", () => "Admin only endpoint")
    .RequireAuthorization("AdminOnly");

// Multiple policies
app.MapGet("/restricted", () => "Adults and admins only")
    .RequireAuthorization("Adults", "AdminOnly");

app.Run();

CORS 中软件

跨大西洋资源分享:

var builder = WebApplication.CreateBuilder(args);

// Configure CORS policies
builder.Services.AddCors(options =>
{
    // Policy 1: Allow all
    options.AddPolicy("AllowAll", policy =>
    {
        policy.AllowAnyOrigin()
              .AllowAnyMethod()
              .AllowAnyHeader();
    });

    // Policy 2: Specific origin
    options.AddPolicy("AllowSpecificOrigin", policy =>
    {
        policy.WithOrigins("https://example.com", "https://app.example.com")
              .AllowAnyMethod()
              .AllowAnyHeader()
              .AllowCredentials(); // Important for cookies/auth
    });

    // Policy 3: Specific methods and headers
    options.AddPolicy("RestrictedAccess", policy =>
    {
        policy.WithOrigins("https://partner.com")
              .WithMethods("GET", "POST")
              .WithHeaders("Content-Type", "Authorization")
              .WithExposedHeaders("X-Custom-Header")
              .SetPreflightMaxAge(TimeSpan.FromMinutes(10));
    });

    // Default policy
    options.AddDefaultPolicy(policy =>
    {
        policy.WithOrigins("https://trustedsite.com")
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

var app = builder.Build();

app.UseRouting();

// Apply CORS globally
app.UseCors("AllowSpecificOrigin");

// Or apply per endpoint
app.MapGet("/api/data", () => new { data = "Hello" })
    .RequireCors("AllowAll");

app.MapGet("/api/restricted", () => new { data = "Restricted" })
    .RequireCors("RestrictedAccess");

app.Run();

CORS 飞行前请求流量 :

sequenceDiagram
    participant Browser
    participant CORS as CORS Middleware
    participant Endpoint

    Browser->>CORS: OPTIONS /api/data<br/>(Preflight Request)
    Note over Browser,CORS: Origin: https://example.com<br/>Access-Control-Request-Method: POST<br/>Access-Control-Request-Headers: Content-Type

    Note over CORS: Check CORS policy

    alt Policy allows
        CORS-->>Browser: 204 No Content<br/>Access-Control-Allow-Origin: https://example.com<br/>Access-Control-Allow-Methods: POST<br/>Access-Control-Allow-Headers: Content-Type
        Browser->>CORS: POST /api/data<br/>(Actual Request)
        CORS->>Endpoint: Forward request
        Endpoint-->>CORS: Response
        CORS-->>Browser: Response + CORS headers
    else Policy denies
        CORS-->>Browser: 403 Forbidden
    end

缓存中中器

为改善绩效而形成的蓄水池反应:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCaching();

var app = builder.Build();

app.UseResponseCaching();

// Cache this endpoint
app.MapGet("/cached", (HttpContext context) =>
{
    context.Response.Headers.CacheControl = "public,max-age=60"; // Cache for 60 seconds
    return $"Generated at {DateTime.UtcNow:HH:mm:ss}";
});

// Don't cache this
app.MapGet("/no-cache", (HttpContext context) =>
{
    context.Response.Headers.CacheControl = "no-cache";
    return $"Generated at {DateTime.UtcNow:HH:mm:ss}";
});

// Conditional caching
app.MapGet("/data", (HttpContext context, string? cache) =>
{
    if (cache == "yes")
    {
        context.Response.Headers.CacheControl = "public,max-age=30";
    }

    return new
    {
        timestamp = DateTime.UtcNow,
        data = "Some data"
    };
});

app.Run();

测试 :

# First request - generates response and caches it
$ curl -i http://localhost:5000/cached
Date: Mon, 15 Jan 2024 10:30:00 GMT
Cache-Control: public,max-age=60

Generated at 10:30:00

# Second request within 60 seconds - served from cache
$ curl -i http://localhost:5000/cached
Date: Mon, 15 Jan 2024 10:30:00 GMT  # Same time!
Cache-Control: public,max-age=60
Age: 15  # Cache age in seconds

Generated at 10:30:00  # Same response!

中软件

压缩对降低带宽的反应:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true; // Enable for HTTPS (be aware of CRIME attack)

    // Providers (order matters - tried in sequence)
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();

    // MIME types to compress
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/json", "text/plain", "text/css", "application/javascript" });
});

// Configure compression levels
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
    options.Level = CompressionLevel.Fastest; // Fastest, Optimal, SmallestSize
});

builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
    options.Level = CompressionLevel.Optimal;
});

var app = builder.Build();

app.UseResponseCompression();

app.MapGet("/large", () =>
{
    // Generate large response
    var data = string.Join("", Enumerable.Repeat("Hello World! ", 1000));
    return Results.Text(data, "text/plain");
});

app.Run();

测试 :

# Without compression
$ curl -H "Accept-Encoding:" http://localhost:5000/large
# Response: ~13KB

# With gzip
$ curl -H "Accept-Encoding: gzip" http://localhost:5000/large
# Response: ~100 bytes (compressed)
# Header: Content-Encoding: gzip

# With brotli (better compression)
$ curl -H "Accept-Encoding: br" http://localhost:5000/large
# Response: ~60 bytes (compressed)
# Header: Content-Encoding: br

创建自定义中继器: 真实世界示例

请求ID 中间软件

在每个追踪请求中添加一个唯一的标识符 :

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

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

    public async Task InvokeAsync(HttpContext context)
    {
        // Check if request already has an ID (from client)
        var requestId = context.Request.Headers["X-Request-ID"].FirstOrDefault()
                       ?? Guid.NewGuid().ToString();

        // Add to response headers
        context.Response.Headers["X-Request-ID"] = requestId;

        // Store in HttpContext for other middleware/endpoints to access
        context.Items["RequestId"] = requestId;

        // Use scope for structured logging
        using (_logger.BeginScope(new Dictionary<string, object>
        {
            ["RequestId"] = requestId
        }))
        {
            _logger.LogInformation("Processing request {RequestId}", requestId);

            await _next(context);

            _logger.LogInformation("Completed request {RequestId}", requestId);
        }
    }
}

API 键密钥验证中软件

API 密钥的自定义验证 :

public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;
    private const string API_KEY_HEADER = "X-API-Key";

    public ApiKeyMiddleware(RequestDelegate next, IConfiguration configuration)
    {
        _next = next;
        _configuration = configuration;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Skip authentication for public endpoints
        if (context.Request.Path.StartsWithSegments("/public"))
        {
            await _next(context);
            return;
        }

        // Check for API key in header
        if (!context.Request.Headers.TryGetValue(API_KEY_HEADER, out var providedKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsJsonAsync(new { error = "API Key is missing" });
            return;
        }

        // Validate API key (in production, check against database)
        var validApiKey = _configuration["ApiKey"];
        if (providedKey != validApiKey)
        {
            context.Response.StatusCode = 403;
            await context.Response.WriteAsJsonAsync(new { error = "Invalid API Key" });
            return;
        }

        // Set user identity based on API key
        var claims = new[] { new Claim("ApiKey", providedKey!) };
        var identity = new ClaimsIdentity(claims, "ApiKey");
        context.User = new ClaimsPrincipal(identity);

        await _next(context);
    }
}

节率限制中软件

每个IP地址的限制要求:

public class RateLimitingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMemoryCache _cache;
    private readonly int _requestLimit;
    private readonly TimeSpan _timeWindow;

    public RateLimitingMiddleware(
        RequestDelegate next,
        IMemoryCache cache,
        int requestLimit = 100,
        int timeWindowSeconds = 60)
    {
        _next = next;
        _cache = cache;
        _requestLimit = requestLimit;
        _timeWindow = TimeSpan.FromSeconds(timeWindowSeconds);
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var clientIp = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        var cacheKey = $"RateLimit_{clientIp}";

        // Get current request count
        var requestCount = _cache.GetOrCreate(cacheKey, entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = _timeWindow;
            return 0;
        });

        if (requestCount >= _requestLimit)
        {
            context.Response.StatusCode = 429; // Too Many Requests
            context.Response.Headers["Retry-After"] = _timeWindow.TotalSeconds.ToString();

            await context.Response.WriteAsJsonAsync(new
            {
                error = "Rate limit exceeded",
                retryAfter = _timeWindow.TotalSeconds
            });
            return;
        }

        // Increment request count
        _cache.Set(cacheKey, requestCount + 1, _timeWindow);

        // Add rate limit headers
        context.Response.Headers["X-RateLimit-Limit"] = _requestLimit.ToString();
        context.Response.Headers["X-RateLimit-Remaining"] = (_requestLimit - requestCount - 1).ToString();
        context.Response.Headers["X-RateLimit-Reset"] = DateTimeOffset.UtcNow.Add(_timeWindow).ToUnixTimeSeconds().ToString();

        await _next(context);
    }
}

// Usage
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
var app = builder.Build();

app.UseMiddleware<RateLimitingMiddleware>(100, 60); // 100 requests per 60 seconds

app.Run();

测试 :

# First request - OK
$ curl -i http://localhost:5000/api/data
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1705318860

# After 100 requests
$ curl -i http://localhost:5000/api/data
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705318860

{"error":"Rate limit exceeded","retryAfter":60}

中件套件

您可根据下列条件分割输油管:

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

// Map: Creates a branch based on path prefix
app.Map("/api", apiApp =>
{
    apiApp.UseMiddleware<ApiKeyMiddleware>();
    apiApp.MapGet("/data", () => new { data = "API data" });
});

// MapWhen: Creates a branch based on custom condition
app.MapWhen(
    context => context.Request.Headers.ContainsKey("X-Custom-Header"),
    customApp =>
    {
        customApp.Use(async (context, next) =>
        {
            context.Response.Headers["X-Custom-Response"] = "Matched!";
            await next(context);
        });

        customApp.MapGet("/special", () => "Special endpoint");
    });

// UseWhen: Rejoins the main pipeline after the branch
app.UseWhen(
    context => context.Request.Path.StartsWithSegments("/admin"),
    adminApp =>
    {
        adminApp.Use(async (context, next) =>
        {
            // Log admin access
            Console.WriteLine("Admin area accessed");
            await next(context);
        });
    });

app.MapGet("/", () => "Main pipeline");

app.Run();

分支可视化 :

graph TD
    A[Request] --> B{Path?}
    B -->|/api/*| C[API Branch]
    B -->|/admin/*| D[Admin Branch UseWhen]
    B -->|Other| E[Main Pipeline]

    C --> F[API Key Middleware]
    F --> G[API Endpoints]
    G --> Z1[Response]

    D --> H[Admin Logging]
    H --> I[Rejoin Main Pipeline]
    I --> J[Main Pipeline Continues]
    J --> Z2[Response]

    E --> K[Main Endpoints]
    K --> Z3[Response]

中中件最佳做法

1. 保持中软件焦点

每个中继器件应有一个单独的责任:

// ❌ Bad: Doing too much
public class BadMiddleware
{
    public async Task InvokeAsync(HttpContext context)
    {
        // Authentication
        // Authorization
        // Logging
        // Rate limiting
        // Response modification
        // ... too much!
    }
}

// ✅ Good: Single responsibility
public class AuthenticationMiddleware { /* Only authentication */ }
public class LoggingMiddleware { /* Only logging */ }
public class RateLimitingMiddleware { /* Only rate limiting */ }

2. 妥善处理例外情况

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

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

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in middleware");

            // Don't swallow exceptions - let exception handler middleware handle them
            throw;
        }
    }
}

3. 尊重反应

回应开始后不要修改 :

public class ResponseSafeMiddleware
{
    private readonly RequestDelegate _next;

    public ResponseSafeMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        //  Good: Check before modifying
        if (!context.Response.HasStarted)
        {
            context.Response.Headers["X-Custom-Header"] = "Value";
        }

        await _next(context);

        //  Bad: Response might have already started
        // context.Response.Headers["X-After-Header"] = "Value"; // This might throw!

        // ✅ Good: Check first
        if (!context.Response.HasStarted)
        {
            context.Response.Headers["X-After-Header"] = "Value";
        }
    }
}

4. 明智地使用依赖性注射

public class DIAwareMiddleware
{
    private readonly RequestDelegate _next;
    //  Singleton services injected in constructor
    private readonly ILogger<DIAwareMiddleware> _logger;

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

    //  Scoped/Transient services injected in InvokeAsync
    public async Task InvokeAsync(HttpContext context, IMyService myService)
    {
        // myService is scoped to this request
        var data = await myService.GetDataAsync();

        await _next(context);
    }
}

密钥外出

  • Midleware是 ASP.NET Core 请求处理的核心。
  • 每件中间器件包紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧紧的
  • 中件订单是关键 - 例外处理程序先, 端点最后
  • Midroware 可以通过不打电话来短路短路 next()
  • 内置中继器械处理常见问题(静态文件、授权、 CORS、缓存、压缩)
  • 自定义中型软件扩展应用程序特定需求框架
  • 使用使用 app.Use() 内线中型, 复杂中型件类
  • Midroware 能够将管道与 Map()MapWhen()
  • 总是检查 Response.HasStarted 在修改信头前
  • 向建筑师输入单子喷射服务,向建筑师提供范围广泛的服务 InvokeAsync()

理解中间软件可以使你完全控制请求的处理方式。 从记录到认证到定制商业逻辑的每一个交叉问题都可以作为中间软件优雅地处理。


继续第4部分:路线和终点,以了解请求如何与终点相匹配,路线系统如何运作。

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.