+3

Xử lý ngoại lệ một cách "tinh tế" trong NET 8

Exception - Ngoại lệ là gì ?

Một ngoại lệ là một tình huống bất thường hoặc không mong muốn xảy ra trong khi một chương trình đang thực thi, như truy cập một tệp không tồn tại, kết nối mạng bị ngắt, hoặc một lỗi logic trong chương trình. Trong .NET, "Exception" là một lớp cơ sở cho tất cả các loại ngoại lệ (exceptions). Lớp Exception là một phần của namespace System và cung cấp cơ sở cho việc xử lý các lỗi trong .NET thông qua cơ chế try - catch các khối lệnh.

Trước đây mình đã làm như thế nào?

Thông thường, trong một dự án Net MVC hay Net API, khi cần xử lý ngoại lệ chúng ta sẽ thực hiện theo cách cơ sở sau

  • Tạo một kiểu Exception của riêng chúng ta (nếu muốn)
  • Tạo một lớp ExceptionHandler để xử lý ngoại lệ theo logic của chúng ta
  • Tạo một middleware để handle ngoại lệ trong luồng chạy
  • Ném ngoại lệ đã tạo ở phần trên cho handler xử lý

Ví dụ: Lớp ApiException - kế thừa từ lớp Exception, đây đại diện cho những ngoại lệ xảy ra khi thực hiện truy vấn API

public class ApiException : Exception
{
    private HttpStatusCode _httpStatusCode;
    public HttpStatusCode StatusCode { get => _httpStatusCode; }

    public ApiException() : base() { }

    public ApiException(string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest) : base(message)
    {
        _httpStatusCode = statusCode;
    }
}

Lớp này sẽ nhận tham số đầu vào là một chuỗi thông báo lỗi và một mã HttpStatusCode trả về dựa vào lỗi (BadRequest, NotFound....)

Xây dựng lớp ExceptionHandlerMiddleware

public class ExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        try
        {
           await _next(context);
        }
        catch (Exception error)
        {
            var response = context.Response;
            response.ContentType = "application/json";
            var responseModel = new
            {
                Message = error?.Message,
                Details = error?.InnerException?.Message
            };

            switch (error)
            {
                case ApiException e:
                    response.StatusCode = (int)e.StatusCode;
                    break;
                default:
                    response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    break;
            }
                var result = JsonConvert.SerializeObject(responseModel);
                await response.WriteAsync(result);
            
        }
    }
}

Lớp này sẽ đại diện cho 1 middleware xử lý Exception nếu có

Đăng ký middleware

app.UseMiddleware<ExceptionHandlerMiddleware>();

Và cuối cùng, trong API, bất cứ lúc nào cần xử lý ngoại lệ, ta cũng có thể ném nó ra

if (user is null) throw new ApiException("User is null", HttpStatusCode.BadRequest);

Trên đây là cách xử lý đơn thuần xưa nay chúng ta - hay ít nhất mình và dự án mình đang áp dụng, bây giờ thử với cách tinh tế hơn nhé !

"Đổi gió" với IExceptionHandler

IExceptionHandler là một interface định nghĩa một hợp đồng cho việc xử lý ngoại lệ. Interface này chủ yếu liên quan đến mô hình xử lý ngoại lệ tập trung thông qua middleware, giúp quản lý cách các ngoại lệ được xử lý trong ứng dụng.

Cách hoạt động: IExceptionHandler là một interface để xử lý các ngoại lệ trong các ứng dụng ASP.NET Core. Các thực thi IExceptionHandler được sử dụng bởi middleware xử lý ngoại lệ. Bằng cách tạo một lớp implement interface này, chúng ta sẽ thay thế việc sử dụng middleware ở cách trên.

Vẫn giữ mã nguồn lớp ApiException như ở trên

Tạo lớp ExceptionHandler implement IExceptionHandler

public class ExceptionHandler : IExceptionHandler
{
    

    public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
    {

        var response = exception switch
        {
            ApiException apiEx => new ProblemDetails
            {
                Status = (int)apiEx.StatusCode,
                Type = apiEx.GetType().Name,
                Title = apiEx.Message,
                Detail = apiEx.Message,
                Instance = $"{httpContext.Request.Method} {httpContext.Request.Path}"
            },
            _ => new ProblemDetails
            {
                Status = StatusCodes.Status500InternalServerError,
                Type = exception.GetType().Name,
                Title = "Internal server error",
                Detail = "Internal server error",
                Instance = $"{httpContext.Request.Method} {httpContext.Request.Path}"
            }
        };

        await httpContext.Response.WriteAsJsonAsync(response);

        return true;
    }
}

Đăng ký ExceptionHandler ở Program.cs hoặc ở nơi bạn đăng ký các DI

builder.Services.AddExceptionHandler<ExceptionHandler>();
builder.Services.AddProblemDetails();
...
**app.UseExceptionHandler(opt => { });** // Dòng này sẽ giúp chúng ta handle tương tự middleware

app.MapGet("/weatherforecast", () =>
{
    throw new ApiException("This is my exception", System.Net.HttpStatusCode.InternalServerError); // ví dụ ném 1 exception
});

Kết quả: image.png

Tổng kết

IExceptionHandler là một cách mới để triển khai handle Exception trong ứng dụng ASP.NET Core, nó giúp chúng ta đơn giản hóa và dễ quản lý các Exception, việc xử lý ngoại lệ lúc này có thể xử lý ở toàn bộ chương trình, thật đơn giản ^^.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí