0Pricing
C# Academy · บทเรียน

การเขียนมิดเดิลแวร์แบบกำหนดเอง

สร้างมิดเดิลแวร์แบบฝังในโค้ดด้วย Use/Run/Map และมิดเดิลแวร์ที่อิงคลาสด้วย InvokeAsync สำหรับคอมโพเนนต์ที่นำกลับมาใช้ซ้ำได้

การเขียนมิดเดิลแวร์แบบกำหนดเอง เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงต้องใช้มิดเดิลแวร์แบบกำหนดเอง

มิดเดิลแวร์แบบกำหนดเองจัดการข้อกังวลร่วมที่ใช้กับคำขอจำนวนมาก เช่น การบันทึก การจับเวลา การจัดการข้อผิดพลาด การยืนยันตัวตน การบีบอัด และการแคช การเขียนเพียงครั้งเดียวทำให้ตรรกะทำงานโดยอัตโนมัติกับทุกคำขอที่ตรงเงื่อนไข

มิดเดิลแวร์แบบแทรกในโค้ดด้วย Use

วิธีที่รวดเร็วที่สุดในการเพิ่มมิดเดิลแวร์คือส่งผู้รับมอบหมายให้กับ app.Use() เหมาะสำหรับการสร้างต้นแบบหรือตรรกะที่เรียบง่ายจริง ๆ

app.Use(async (context, next) =>
{
    var start = DateTimeOffset.UtcNow;

    await next(context);

    var elapsed = DateTimeOffset.UtcNow - start;
    context.Response.Headers.Append(
        "X-Elapsed", elapsed.TotalMilliseconds.ToString("F0") + "ms");
});

มิดเดิลแวร์แบบคลาสตามแบบแผน

คลาสมิดเดิลแวร์ตามแบบแผนมีตัวสร้างที่รับ RequestDelegate และมีเมธอดสาธารณะ InvokeAsync พารามิเตอร์เพิ่มเติมของตัวสร้างสามารถถูกฉีดจาก DI ได้

public class SecurityHeadersMiddleware
{
    private readonly RequestDelegate _next;

    public SecurityHeadersMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var headers = context.Response.Headers;
        headers.Append("X-Content-Type-Options", "nosniff");
        headers.Append("X-Frame-Options", "DENY");
        headers.Append("X-XSS-Protection", "1; mode=block");
        headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");

        await _next(context);
    }
}

// Extension method for clean registration:
public static class SecurityHeadersExtensions
{
    public static IApplicationBuilder UseSecurityHeaders(
        this IApplicationBuilder app) =>
        app.UseMiddleware<SecurityHeadersMiddleware>();
}

IMiddleware สำหรับการพึ่งพาตามขอบเขต DI

หากมิดเดิลแวร์ของคุณต้องใช้บริการตามขอบเขต เช่น DbContext ให้ใช้อินเทอร์เฟซ IMiddleware ลงทะเบียนเป็นบริการตามขอบเขต แล้วเฟรมเวิร์กจะสร้างการพึ่งพาใหม่สำหรับแต่ละคำขอ

public class AuditMiddleware : IMiddleware
{
    private readonly AuditDbContext _db;
    private readonly IHttpContextAccessor _http;

    public AuditMiddleware(AuditDbContext db, IHttpContextAccessor http)
    {
        _db = db;
        _http = http;
    }

    public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
    {
        await next(ctx);

        if (ctx.Request.Method != "GET")
        {
            _db.AuditLogs.Add(new AuditLog
            {
                User   = ctx.User.Identity?.Name,
                Path   = ctx.Request.Path,
                Status = ctx.Response.StatusCode,
                At     = DateTime.UtcNow
            });
            await _db.SaveChangesAsync();
        }
    }
}

builder.Services.AddScoped<AuditMiddleware>();
app.UseMiddleware<AuditMiddleware>();

มิดเดิลแวร์พร้อมตัวเลือก

ส่งการกำหนดค่าไปยังมิดเดิลแวร์ผ่านออบเจ็กต์ตัวเลือกในตัวสร้าง ลงทะเบียนตัวเลือกกับคอนเทนเนอร์ DI หรือส่งค่าโดยตรงขณะลงทะเบียนมิดเดิลแวร์

public class ThrottleOptions
{
    public int MaxRequestsPerSecond { get; set; } = 100;
    public string[] ExcludedPaths { get; set; } = Array.Empty<string>();
}

public class ThrottleMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ThrottleOptions _options;

    public ThrottleMiddleware(RequestDelegate next, ThrottleOptions options)
    {
        _next   = next;
        _options = options;
    }
    // ...
}

// Registration with options:
app.UseMiddleware<ThrottleMiddleware>(new ThrottleOptions
{
    MaxRequestsPerSecond = 50
});

มิดเดิลแวร์จัดการข้อยกเว้น

มิดเดิลแวร์จัดการข้อยกเว้นแบบกำหนดเองจะดักจับข้อยกเว้นที่ไม่ได้จัดการทั้งหมด และส่งการตอบกลับข้อผิดพลาดที่มีโครงสร้างกลับไป

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

    public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> l)
    {
        _next = next;
        _logger = l;
    }

    public async Task InvokeAsync(HttpContext ctx)
    {
        try { await _next(ctx); }
        catch (NotFoundException ex)
        {
            ctx.Response.StatusCode = 404;
            await ctx.Response.WriteAsJsonAsync(new { error = ex.Message });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unhandled exception");
            ctx.Response.StatusCode = 500;
            await ctx.Response.WriteAsJsonAsync(new { error = "Internal server error" });
        }
    }
}

มิดเดิลแวร์บันทึกคำขอ

มิดเดิลแวร์บันทึกคำขอและการตอบกลับแบบมีโครงสร้างจะบันทึกเมธอด เส้นทาง รหัสสถานะ และเวลาที่ใช้ของทุกคำขอ

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _log;

    public RequestLoggingMiddleware(RequestDelegate n, ILogger<RequestLoggingMiddleware> l)
    { _next = n; _log = l; }

    public async Task InvokeAsync(HttpContext ctx)
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();
        await _next(ctx);
        sw.Stop();

        _log.LogInformation(
            "{Method} {Path} -> {Status} in {Ms}ms",
            ctx.Request.Method,
            ctx.Request.Path,
            ctx.Response.StatusCode,
            sw.ElapsedMilliseconds);
    }
}

มิดเดิลแวร์แบบมีเงื่อนไขด้วย UseWhen

UseWhen จะแยกไปป์ไลน์ตามเงื่อนไขโดยอิงจากเงื่อนไขหนึ่ง โดยไม่แยกไปป์ไลน์ออกจากกันอย่างถาวร หลังจากทำงานเสร็จ แขนงจะแนบกลับเข้าสู่ไปป์ไลน์หลัก

// Apply authentication middleware only to /api/* routes
app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/api"),
    apiApp =>
    {
        apiApp.UseAuthentication();
        apiApp.UseAuthorization();
    });

// Public routes (/public/*) bypass authentication
app.MapGet("/public/health", () => "OK");
app.MapGet("/api/data", [Authorize] async (DataService s) => await s.GetAsync());

มิดเดิลแวร์แคชการตอบกลับ

มิดเดิลแวร์แคชการตอบกลับที่มีมาให้จะเก็บการตอบกลับทั้งหมดไว้ในหน่วยความจำ กำหนดนโยบายแคชสำหรับแต่ละจุดปลายทางได้โดยใช้ส่วนหัวหรือแอตทริบิวต์

builder.Services.AddResponseCaching();

app.UseResponseCaching();

app.MapGet("/products", async (AppDbContext db) =>
{
    var products = await db.Products.AsNoTracking().ToListAsync();
    return Results.Ok(products);
})
.CacheOutput(p => p.Expire(TimeSpan.FromMinutes(2)).Tag("products"));

กรณีใช้งานจริง: มิดเดิลแวร์โหมดบำรุงรักษา

มิดเดิลแวร์โหมดบำรุงรักษาจะอ่านสถานะจากการกำหนดค่า ซึ่งรองรับการโหลดใหม่ขณะทำงานผ่าน IOptionsMonitor และปฏิเสธคำขอที่ไม่ใช่การตรวจสอบสุขภาพระหว่างการบำรุงรักษา

public class MaintenanceMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IOptionsMonitor<MaintenanceOptions> _options;

    public MaintenanceMiddleware(RequestDelegate next,
        IOptionsMonitor<MaintenanceOptions> opts)
    { _next = next; _options = opts; }

    public async Task InvokeAsync(HttpContext ctx)
    {
        if (_options.CurrentValue.IsEnabled
            && !ctx.Request.Path.StartsWithSegments("/health"))
        {
            ctx.Response.StatusCode = 503;
            ctx.Response.Headers.Append("Retry-After", "300");
            await ctx.Response.WriteAsJsonAsync(
                new { message = "Service under maintenance" });
            return;
        }
        await _next(ctx);
    }
}

ตรวจสอบความเข้าใจ

เหตุใดจึงควรใช้อินเทอร์เฟซ IMiddleware แทนแนวทางตามแบบแผนเมื่อต้องใช้บริการตามขอบเขต เช่น DbContext

สรุป: การเขียนมิดเดิลแวร์แบบกำหนดเอง

ประเด็นสำคัญ:

  • แบบแทรกในโค้ด (ผู้รับมอบหมายของ Use): รวดเร็ว เหมาะกับโค้ดสั้น ๆ; แบบคลาส: นำกลับมาใช้ใหม่ได้และทดสอบได้
  • คลาสตามแบบแผน: ตัวสร้างรับ RequestDelegate; InvokeAsync(HttpContext)
  • IMiddleware: แก้ไขการพึ่งพาผ่าน DI ต่อคำขอ ซึ่งจำเป็นสำหรับการพึ่งพาตามขอบเขต
  • UseWhen: การแยกแขนงตามเงื่อนไขที่กลับมารวมกับไปป์ไลน์หลัก
  • เขียนมิดเดิลแวร์จัดการข้อยกเว้นเป็นอันดับแรก ซึ่งเป็นชั้นนอกสุด เพื่อดักจับข้อผิดพลาดทั้งหมด
  • สร้างเมธอดส่วนขยาย (UseXxx) เพื่อให้การลงทะเบียนสะอาดและค้นหาได้ง่าย

คำถามที่พบบ่อย

บทเรียน “การเขียนมิดเดิลแวร์แบบกำหนดเอง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การเขียนมิดเดิลแวร์แบบกำหนดเอง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การเขียนมิดเดิลแวร์แบบกำหนดเอง”

สร้างมิดเดิลแวร์แบบฝังในโค้ดด้วย Use/Run/Map และมิดเดิลแวร์ที่อิงคลาสด้วย InvokeAsync สำหรับคอมโพเนนต์ที่นำกลับมาใช้ซ้ำได้ คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การเขียนมิดเดิลแวร์แบบกำหนดเอง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม

ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ภาพรวมสายงาน ASP.NET Core
  2. การเขียนมิดเดิลแวร์แบบกำหนดเอง
  3. การตัดการทำงานระยะสั้นและการแตกแขนง
  4. ลำดับมิดเดิลแวร์และมิดเดิลแวร์ในตัว
← กลับไปที่ C# Academy