Nguon: Microsoft Learn · .NET 8.0

Xử lý lỗi trong ASP.NET Core

Nguồn: Handle errors in ASP.NET Core

Developer Exception Page (Trang ngoại lệ dành cho developer)

Developer Exception Page hiển thị thông tin chi tiết về các unhandled request exception (ngoại lệ request không được xử lý) bằng cách sử dụng DeveloperExceptionPageMiddleware. Nó được kích hoạt theo mặc định khi:

Cảnh báo: Không kích hoạt Developer Exception Page trừ khi ứng dụng đang chạy trong môi trường Development. Không bao giờ chia sẻ thông tin exception chi tiết trong production (môi trường sản xuất).

Trang hiển thị:

Đối với request có header Accept: text/plain, nó trả về plain text thay vì HTML.

Exception Handler Page (Trang xử lý ngoại lệ)

Cấu hình xử lý lỗi tùy chỉnh cho môi trường Production bằng cách sử dụng UseExceptionHandler:

csharp
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Middleware này:

Quan trọng: Middleware phải xử lý reentrancy (vào lại). Khi re-executing request pipeline:

Truy cập Exception

Sử dụng IExceptionHandlerPathFeature để truy cập thông tin chi tiết exception:

csharp
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Exception Handler Lambda

Thay thế cho exception handler page, sử dụng lambda với UseExceptionHandler:

csharp
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = "text/plain";

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Đặt status code dựa trên loại exception:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        StatusCodeSelector = ex => ex is TimeoutException
            ? StatusCodes.Status503ServiceUnavailable
            : StatusCodes.Status500InternalServerError
    });
}

IExceptionHandler

IExceptionHandler cung cấp callback tập trung để xử lý các exception đã biết:

csharp
public class CustomExceptionHandler : IExceptionHandler
{
    private readonly ILogger<CustomExceptionHandler> logger;
    
    public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
    {
        this.logger = logger;
    }

    public ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        var exceptionMessage = exception.Message;
        logger.LogError(
            "Error Message: {exceptionMessage}, Time of occurrence {time}",
            exceptionMessage, DateTime.UtcNow);
        
        // Trả về false để tiếp tục với hành vi mặc định
        // Trả về true để báo hiệu exception đã được xử lý
        return ValueTask.FromResult(false);
    }
}

Đăng ký trong dependency injection:

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

UseStatusCodePages

Kích hoạt các handler chỉ text mặc định cho các HTTP error status code:

csharp
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Gọi UseStatusCodePages trước request handling middleware (Static Files, Endpoints).

Với Format String

csharp
app.UseStatusCodePages("text/plain", "Status Code Page: {0}");

Với Lambda

csharp
app.UseStatusCodePages(async statusCodeContext =>
{
    statusCodeContext.HttpContext.Response.ContentType = "text/plain";

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePagesWithRedirects

Gửi 302 Found và redirect đến error endpoint:

csharp
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

Sử dụng khi ứng dụng nên redirect đến endpoint khác.

UseStatusCodePagesWithReExecute

Re-execute (thực thi lại) request pipeline bằng cách sử dụng alternate path mà không thay đổi status code:

csharp
app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

// Hoặc với tham số query string
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

Truy cập URL gốc trong error handler:

csharp
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }
    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
                                    + $"{statusCodeReExecuteFeature.OriginalPath}"
                                    + $"{statusCodeReExecuteFeature.OriginalQueryString}";
        }
    }
}

Vô hiệu hóa Status Code Pages

Sử dụng attribute [SkipStatusCodePages] trên controller/action:

csharp
public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Exception Filters (Bộ lọc ngoại lệ)

Trong MVC, exception filter có thể được cấu hình global (toàn cục) hoặc theo controller/action. Trong Razor Pages, global hoặc theo page model. Exception filter xử lý các exception không được xử lý trong quá trình thực thi nhưng không linh hoạt bằng exception handling middleware.

Problem Details (Chi tiết vấn đề)

Implement định dạng RFC 7807 Problem Details cho API error.

Đăng ký service:

csharp
builder.Services.AddProblemDetails();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

Các middleware sau tạo ra problem details response khi AddProblemDetails được gọi:

Tùy chỉnh Problem Details

Sử dụng CustomizeProblemDetails:

csharp
builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

Ví dụ response 400 Bad Request:

json
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

Custom IProblemDetailsWriter

csharp
public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Đăng ký trước khi gọi AddRazorPages, AddControllers, v.v.:

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

Problem Details trong Middleware

Sử dụng IProblemDetailsService.WriteAsync:

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

app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
            { } problemDetailsService)
        {
            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = "Invalid input",
                    Type = "https://errors.example.com/badInput"
                }
            });
        }
    }
});

Xử lý ngoại lệ phía Server

HTTP server implementation xử lý exception trước khi response header được gửi:

Xử lý ngoại lệ Startup

Chỉ có hosting layer xử lý startup exception. Host có thể được cấu hình để:

Nếu binding thất bại, hosting layer log exception nghiêm trọng và process crash. Không có error page nào hiển thị với Kestrel.

Trên IIS/Azure App Service, 502.5 - Process Failure được trả về bởi ASP.NET Core Module nếu process không thể khởi động.

Database Error Page

AddDatabaseDeveloperPageExceptionFilter capture các database-related exception có thể giải quyết bằng Entity Framework Core migration (chỉ trong môi trường Development):

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Lưu ý bảo mật quan trọng

Cảnh báo: Không phục vụ thông tin lỗi nhạy cảm cho client. Phục vụ lỗi là một rủi ro bảo mật.

Tài nguyên bổ sung