Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C#

วิธาจัดการ Error ใน Middleware C# ASP.NET Core

การสร้าง Global Exception Handling Middleware ใน ASP.NET Core

Middleware เป็นจุดกลางสำหรับจัดการ HTTP Request Pipeline การสร้าง Custom Exception Middleware ช่วยจัดการข้อผิดพลาดแบบรวมศูนย์ (Centralized) ให้สม่ำเสมอทั่วแอปพลิเคชัน

โครงสร้าง Exception Middleware

using System;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

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

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

    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);  // เรียก middleware ถัดไป
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception occurred");
            await HandleExceptionAsync(httpContext, ex);
        }
    }

    private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        
        var (statusCode, message) = exception switch
        {
            ArgumentNullException => (HttpStatusCode.BadRequest, "Required parameter is missing"),
            ArgumentException => (HttpStatusCode.BadRequest, exception.Message),
            KeyNotFoundException => (HttpStatusCode.NotFound, "Resource not found"),
            UnauthorizedAccessException => (HttpStatusCode.Unauthorized, "Unauthorized access"),
            _ => (HttpStatusCode.InternalServerError, "An internal server error occurred")
        };

        context.Response.StatusCode = (int)statusCode;

        var response = new ErrorResponse
        {
            StatusCode = statusCode,
            Message = message,
            // ระบุ Path เพื่อช่วย debug
            Path = context.Request.Path
        };

        var json = JsonSerializer.Serialize(response, new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        });

        await context.Response.WriteAsync(json);
    }
}

public class ErrorResponse
{
    public HttpStatusCode StatusCode { get; set; }
    public string Message { get; set; }
    public string Path { get; set; }
    public string TraceId { get; set; } = System.Diagnostics.Activity.Current?.Id;
}

Extension Method เพื่อง่ายต่อการใช้งาน

using Microsoft.AspNetCore.Builder;

public static class ExceptionMiddlewareExtensions
{
    public static IApplicationBuilder UseCustomExceptionHandler(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ExceptionMiddleware>();
    }
}

ลงทะเบียนใน Program.cs (.NET 6+) หรือ Startup.cs

// Program.cs (.NET 6+)
var builder = WebApplication.CreateBuilder(args);

// เพิ่ม logging
builder.Services.AddLogging();

var app = builder.Build();

// ใช้ Exception Middleware (ควรอยู่บนสุดของ pipeline)
app.UseCustomExceptionHandler();

// Middleware อื่นๆ
app.UseRouting();
app.UseAuthorization();
app.MapControllers();

app.Run();

Built-in Exception Handling (ทางเลือกง่ายกว่า)

ASP.NET Core มี middleware ตัวจัดการ error built-in แล้ว:

1. Developer Exception Page (Development only)

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();  // แสดง stack trace เต็ม
}

2. Exception Handler (Production)

// นำทางไปยัง error handling endpoint
app.UseExceptionHandler("/error");

// สร้าง Error Controller
[ApiController]
[Route("error")]
public class ErrorController : ControllerBase
{
    [HttpGet]
    public IActionResult Error()
    {
        var exception = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error;
        var logger = HttpContext.RequestServices.GetService<ILogger<ErrorController>>();
        logger?.LogError(exception, "Unhandled exception");
        
        return Problem(
            title: "An error occurred",
            statusCode: 500,
            detail: exception?.Message
        );
    }
}

3. Status Code Pages

// แปลง status code 404, 500 เป็น response ที่อ่านง่าย
app.UseStatusCodePages(async context =>
{
    context.HttpContext.Response.ContentType = "application/json";
    await context.HttpContext.Response.WriteAsync(
        JsonSerializer.Serialize(new { 
            Error = context.HttpContext.Response.StatusCode == 404 ? "Not Found" : "Error",
            StatusCode = context.HttpContext.Response.StatusCode
        })
    );
});

// หรือใช้กับ Razor Pages
app.UseStatusCodePagesWithReExecute("/Error/{0}");

แนวทางปฏิบัติที่ดี (Best Practices)

หลักการคำอธิบาย
Log ExceptionLog ทุก exception พร้อม context (TraceId, User, Path)
Don't Leak DetailsProduction: ไม่คืน stack trace/internal detail ให้ client
Consistent Formatใช้ response format เดียวกันทั่วแอป (RFC 7807 ProblemDetails)
Handle Specific ExceptionsMap exception ประเภทต่างๆ ไป status code ที่เหมาะสม
Order Mattersวาง Exception Middleware บนสุด (UseExceptionHandler อยู่บนสุด)

ProblemDetails (RFC 7807) - มาตรฐานสำหรับ API Error

// ใช้ built-in ProblemDetails (ASP.NET Core 3.1+)
app.UseExceptionHandler("/error");

[ApiController]
public class ErrorController : ControllerBase
{
    [Route("error")]
    public IActionResult Error()
    {
        var feature = HttpContext.Features.Get<IExceptionHandlerFeature>();
        var problem = new ProblemDetails
        {
            Title = "An error occurred",
            Status = 500,
            Detail = feature?.Error.Message,
            Instance = HttpContext.Request.Path,
            Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1"
        };
        
        // เพิ่มข้อมูล custom
        problem.Extensions["traceId"] = System.Diagnostics.Activity.Current?.Id;
        
        return new ObjectResult(problem) { StatusCode = 500 };
    }
}

สรุป

  • Custom Middleware - ควบคุมได้เต็มที่ เหมาะกับ complex logic, logging แบบกำหนดเอง
  • UseExceptionHandler - เรียบง่าย พอสำหรับส่วนใหญ่ ใช้กับ Error Controller
  • UseDeveloperExceptionPage - เฉพาะ Development เท่านั้น
  • ProblemDetails - มาตรฐานสากล สำหรับ REST API