Κατανοώντας τον κεντρικό αγωγό ASP.NET Αίτηση και απάντηση - Μέρος 3: Μεσαίος αγωγός (ελληνικά (Greek))

Κατανοώντας τον κεντρικό αγωγό ASP.NET Αίτηση και απάντηση - Μέρος 3: Μεσαίος αγωγός

Sunday, 09 November 2025

//

18 minute read

Εισαγωγή

Εξερευνήσαμε το στρώμα φιλοξενίας και το διακομιστή Kestrel στο Μέρος 2. Τώρα φτάνουμε στην καρδιά του πυρήνα ASP.NET: ο αγωγός μέσου λογισμικού. Εδώ ζει η λογική της εφαρμογής σας, όπου τα αιτήματα υποβάλλονται σε επεξεργασία, και όπου κατασκευάζονται οι απαντήσεις.

Το Middleware είναι κομψά απλό στην έννοιαΤο κάθε συστατικό είναι μια λειτουργία που επεξεργάζεται ένα αίτημα HTTP αρκετά ισχυρό για να χειριστεί τα πάντα από την εξακρίβωση της ταυτότητας έως τον χειρισμό λάθους μέχρι την εξυπηρέτηση αρχείων. Σε αυτό το μέρος, θα εξερευνήσουμε πώς λειτουργεί το μεσαίο λογισμικό, θα εξετάσουμε τα ενσωματωμένα συστατικά και θα μάθουμε να δημιουργούμε τα δικά μας.

ΣΗΜΕΙΩΣΗ: Αυτό είναι μέρος μιας σειράς πειραματικών άρθρων (που χρονολογούνται στο παρελθόν).

Τι είναι το Middleware;

Το 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]

Κάθε μεσαίο λογισμικό μπορεί:

  • Επεξεργαστείτε το αίτημα πριν το περάσετε στο επόμενο μεσαίο λογισμικό
  • Σύντομο κύκλωμα και να επιστρέψει μια απάντηση αμέσως
  • Επεξεργαστείτε την απόκριση καθώς επιστρέφει μέσω του αγωγού

Η υπογραφή του εκπροσώπου του Middleware

Στον πυρήνα του, το μεσαίο λογισμικό είναι απλά μια συνάρτηση με αυτή την υπογραφή:

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()
  • Το μεσαίο λογισμικό μπορεί να βραχυκυκλώσει χωρίς κλήση next()

Δημιουργία μεσαίου λογισμικού: Τρεις προσεγγίσεις

1. Inline Middleware με Use()

Η απλούστερη προσέγγιση χρησιμοποιεί εκφράσεις λάμδα:

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. Terminal Middleware με 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. Class-Based Middleware

Για πολύπλοκο μεσαίο λογισμικό, χρησιμοποιήστε μια τάξη:

// 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. ΧΣΤΣConstellation name (optional, probably does not need a translation) - Επικεφαλίδα ασφαλείας που πρέπει να τεθεί νωρίς
  3. Επανακατευθύνσεις HTTPS - Επαναπροσανατολιστείτε στο HTTPS πριν από την επεξεργασία
  4. Στατικά αρχεία - Μπορεί να βραχυκυκλώσει νωρίς για στατικό περιεχόμενο
  5. ΡούτινγκCity name (optional, probably does not need a translation) - Ταιριάζει το αίτημα με ένα τελικό σημείο
  6. ΚΟΡΣ - Πρέπει να είναι μετά Routing και πριν από την πιστοποίηση
  7. Ταυτοποίηση -Αναγνωρίζει ποιος είναι ο χρήστης
  8. Εξουσιοδότηση - Ορίζει τι μπορεί να κάνει ο χρήστης
  9. Προσαρμοσμένο μεσαίο λογισμικό - Η λογική της εφαρμογής σας
  10. Σύνοδος - Διαχείριση του κράτους συνεδρίας
  11. Απάντηση Caching - Απαντήσεις στην κρυψώνα
  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();

Ενσωματωμένο μεσαίο λογισμικό βαθιά κατάδυση

Exception Handler Middleware

Αφαιρούν εξαιρέσεις από τα μεταγενέστερα μεσαία λογισμικά και παράγουν απαντήσεις λάθους:

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": "..."
  }
}

Στατικά αρχεία Middleware

Σερβίρει στατικά αρχεία και βραχυκυκλώνει τον αγωγό:

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

Μεσαίο λογισμικό ταυτοποίησηςName

Επιβεβαιώνει τον χρήστη με βάση το αίτημα:

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 Middleware

Χειρισμοί Cross-Origin κοινή χρήση πόρων:

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

Απάντηση Caching Middleware

Συγκρατεί τις απαντήσεις για τη βελτίωση της απόδοσης:

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 Middleware

Προσθέτει ένα μοναδικό αναγνωριστικό σε κάθε αίτηση για εντοπισμό:

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);
    }
}

Απομακρύνσεις κλειδιών@ title: window

  • Το Middleware είναι η καρδιά της επεξεργασίας αιτήσεων στο ASP.NET Core
  • Κάθε μεσαίο λογισμικό τυλίγει το επόμενο, δημιουργώντας μια φωλιασμένη αλυσίδα
  • Η εντολή του μεσαίου λογισμικού είναι κρίσιμης σημασίας για τους χειριστές εξαίρεσης πρώτα, τα τελικά σημεία διαρκούν
  • Το μεσαίο λογισμικό μπορεί να βραχυκυκλώσει χωρίς κλήση next()
  • Ενσωματωμένο μεσαίο λογισμικό χειρίζεται κοινές ανησυχίες (στατικά αρχεία, auth, CORS, caching, συμπίεση)
  • Προσαρμοσμένο μεσαίο λογισμικό επεκτείνει το πλαίσιο για ειδικές ανάγκες εφαρμογής
  • Χρήση app.Use() για inline middleware, κατηγορίες για σύνθετα middleware
  • Το μεσαίο λογισμικό μπορεί να διακλαδίσει τον αγωγό με Map() και MapWhen()
  • Πάντα να ελέγχετε Response.HasStarted πριν από την τροποποίηση κεφαλίδων
  • Εισάγετε υπηρεσίες singleton στον κατασκευαστή, υπηρεσίες που εμπίπτουν στο πεδίο εφαρμογής InvokeAsync()

Κατανόηση μεσαίο λογισμικό σας δίνει πλήρη έλεγχο σχετικά με το πώς τα αιτήματα υποβάλλονται σε επεξεργασία.


Συνεχίστε στο Μέρος 4: Routing και Endpoints για να μάθετε πώς τα αιτήματα ταιριάζουν με τα τελικά σημεία και πώς λειτουργεί το σύστημα δρομολόγησης.

Finding related posts...
logo

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