Xử lý ngoại lệ với Exception Handler Middleware trong ASP.NET Core

Tất cả chúng ta đều muốn tin rằng các ứng dụng của chúng ta không có lỗi và sẽ chạy mà không có ngoại lệ. Những nhà phát triển đã làm đủ lâu sẽ biết rằng người dùng sẽ tìm ra cách để phá vỡ các cơ sở mã được kiểm tra tỉ mỉ nhất của chúng ta. Ngoài việc kiểm tra kỹ lưỡng, chúng ta cũng nên tính đến các trường hợp ngoại lệ không mong muốn.

Trong ASP.NET Core, chúng ta có thể xử lý các ngoại lệ đó bằng cách sử dụng middleware ExceptionHandlerMiddleware đi kèm với web framework.

Bài viết này sẽ chỉ ra cách chúng ta có thể sử dụng middleware theo hai cách khác nhau để xử lý các ngoại lệ toàn cục. Tất cả các phương pháp đều hoạt động tương tự nhau, nhưng chúng ta sẽ nói về ưu điểm của từng phương pháp và tại sao chúng ta muốn sử dụng nó cho ứng dụng của mình.

Middleware và Request Pipeline

Chúng tôi sẽ giới thiệu tổng quan nhanh cho những người không quen với hoạt động bên trong của ASP.NET Core.

Ứng dụng của chúng ta sẽ xử lý tất cả các yêu cầu đến thông qua Request Pipeline (đường ống yêu cầu) mà chúng ta thường thiết lập trong phương thức Configure của class Startup.

ASP.NET Core sẽ gọi đường ống của chúng ta bằng cách sử dụng cái mà hầu hết các nhà phát triển gọi là Mô hình búp bê Nga. Mỗi thành phần đã đăng ký được gọi bởi thành phần trước và có khả năng gọi thành phần tiếp theo.

Request Pipeline của chúng ta có thể bao gồm các middleware (phần mềm trung gian), ASP.NET Core endpoint (điểm cuối) và các framework cấp cao như ASP.NET Core MVC.

Mỗi middleware có cơ hội xử lý yêu cầu đến, gọi middleware tiếp theo hoặc chấm dứt yêu cầu HTTP bằng cách trả về một phản hồi. Hiểu request pipeline là điều cần thiết, vì nó chỉ định nơi chúng ta nên đăng ký middleware xử lý ngoại lệ của mình.

Nếu chúng ta đăng ký middleware xử lý ngoại lệ quá muộn, thì sẽ có thể bỏ lỡ các trường hợp ngoại lệ.

Vấn đề của endpoint

Để hiểu cách xử lý một ngoại lệ (exception), trước tiên hãy tạo một endpoint hoạt động sai trong ứng dụng ASP.NET Core của chúng ta.

Trong cuộc gọi tới phương thức UseEndpoints, chúng ta sẽ thêm một endpoint bị lỗi ngẫu nhiên. Chúng ta sẽ xem phần sau cách truy xuất ngoại lệ đã ném bằng cách sử dụng tính năng IExceptionHandlerPathFeature.

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
    
    endpoints.MapGet("/", async context =>
    {
        var random = new Random();

        throw random.Next(1, 4) switch
        {
            1 => new ArgumentException("what were you thinking?!", nameof(random)),
            2 => new DataException("New exception, who dis?"),
            _ => new ArithmeticException("1 + 1 is 4")
        };
        
    });
});

Khi chúng ta bắt đầu ứng dụng của mình, chúng ta nên ngay lập tức ném một trong ba trường hợp ngoại lệ được hiển thị ở trên.

Xử lý các trường hợp ngoại lệ bằng PathString

Cách tiếp cận đơn giản nhất để xử lý các ngoại lệ trong ứng dụng ASP.NET Core của chúng ta là đăng ký middleware ExceptionHandlerMiddleware bằng cách sử dụng PathString.

Chúng ta có thể thêm middleware bằng cách gọi phương thức mở rộng UseExceptionHandler trong phương thức Configure của class Startup. Hãy nhớ rằng, thứ tự của middleware là điều rất quan trọng.

Chúng ta cần thực hiện cuộc gọi đăng ký middleware xử lý ngoại lệ trước tất cả các middleware khác nếu chúng ta mong đợi trình xử lý sẽ xử lý lỗi một cách thích hợp.

app.UseExceptionHandler("/error");

Trong trường hợp này, chúng ta yêu cầu ASP.NET Core thực thi đường dẫn mà chúng ta đã cung cấp thay cho yêu cầu không thành công. Middleware ExceptionHandlerMiddleware sẽ gửi một yêu cầu mới đến đường dẫn /error của chúng ta.

Trong ví dụ này, chúng ta sẽ định nghĩa một trang ** Razor Pages ** tại điểm cuối /error này.

using System;
using System.Data;
using System.Net;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace WebApplication8.Pages
{
    public class Error : PageModel
    {
        public void OnGet()
        {
            var ctx = HttpContext;
            var feature = ctx.Features.Get<IExceptionHandlerPathFeature>();
            ctx.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
            var exception = feature.Error;
            string message = exception switch
            {
                ArgumentException ae => $"{ae.ParamName} & {ae.Message}",
                DataException de => $"{de.Message}",
                ArithmeticException are => $"{are.Message}",
                _ => "oops!"
            };

            Message = message;
        }

        public string Message { get; set; }
    }
}

Razor Page của chúng ta có thể truy xuất ngoại lệ bằng cách sử dụng tính năng IExceptionHandlerPathFeature. Chúng ta có thể truy xuất tính năng từ danh sách HttpContext.Features, tại thời điểm đó chúng ta cũng có quyền truy cập vào tính năng Exception mà ứng dụng của chúng ta đã ném ra.

Ưu điểm của cách tiếp cận PathString

Ưu điểm đáng kể nhất của cách tiếp cận này là tính đơn giản của nó. Trình xử lý ngoại lệ không phụ thuộc vào bất kỳ framework nào nằm ở đầu kia của đường dẫn.

ASP.NET Core MVC, Carter hoặc các framework khác có thể sẵn sàng để xử lý ngoại lệ. Trong ví dụ của chúng ta, chúng ta đã sử dụng Razor Pages để hiển thị thông báo lỗi.

Ưu điểm bổ sung là trình xử lý thực thi tại chỗ, bảo toàn đường dẫn yêu cầu cho máy khách.

Nhược điểm của cách tiếp cận PathString

Điểm bất lợi đáng kể nhất là trình xử lý ngoại lệ sẽ sử dụng lại định nghĩa đường ống hiện có nơi xảy ra lỗi.

Ngoại lệ có thể đã xảy ra do một thành phần middleware được cấu hình sai. Nếu trường hợp này xảy ra, chúng ta có thể bị mắc kẹt trong trạng thái xử lý lỗi đệ quy, do đó phá vỡ ứng dụng của chúng ta.

Mặc dù có thể không xảy ra trường hợp này, nhưng vẫn có khả năng các nhà phát triển ASP.NET Core sẽ cần lưu ý. Vậy chúng ta có thể làm gì về vấn đề này?

Xử lý các trường hợp ngoại lệ với một đường ống mới

Một tính năng ít được biết đến của phương thức UseExceptionHandler là nó có nhiều phương thức quá tải. Một trong những phương thức quá tải đó có một đối số là interface IApplicationBuilder, điều này cho phép chúng ta định nghĩa lại một request pipeline riêng biệt.

Tại đây chúng ta có thể thêm hoặc bớt middleware có thể là nguồn gốc của các trường hợp ngoại lệ. Hãy xem nó trong đoạn mã dưới đây.

// define exception handler pipeline this will create a brand
//  new pipeline separate from the original failed pipeline
app.UseExceptionHandler(builder =>
{
    // define brand new pipeline
    
    builder.Use(async (ctx, next) =>
    {
        var feature = ctx.Features.Get<IExceptionHandlerPathFeature>();
        ctx.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
        var exception = feature.Error;

        string message = exception switch
        {
            ArgumentException ae => $"{ae.ParamName} & {ae.Message}",
            DataException de => $"{de.Message}",
            ArithmeticException are => $"{are.Message}",
            _ => "oops!"
        };

        await ctx.Response.WriteAsync(message);        
    });
});

Trong đoạn mã trên, chúng ta định nghĩa đường dẫn của chúng ta là một RequestDelegate duy nhất, không đề cập đến các framework hoặc middleware khác.

Ưu điểm của cách tiếp cận đường ống mới

Ưu điểm đáng kể nhất của cách tiếp cận này là khả năng định nghĩa một đường ống được sắp xếp hợp lý cho các trường hợp ngoại lệ.

Phương pháp này hữu ích để giảm thiểu sự xuất hiện của các lỗi tiếp theo và giảm lượng thời gian cần thiết để phản hồi với một ngoại lệ. Càng ít middleware, càng ít cuộc gọi đến các phương thức cần thực thi.

Nhược điểm của cách tiếp cận đường ống mới

Bất lợi đáng kể nhất là cần phải định nghĩa lại đường ống mới. Trong khi định nghĩa lại đường ống mới, chúng ta có thể quên bao gồm một số phần quan trọng trong ứng dụng của chúng ta. Tệ hơn nữa, chúng ta có thể vô tình hoán đổi thứ tự hai middleware, tạo ra một lỗi thứ tự rất tinh vi.

Phần kết luận

Trong bài viết này, chúng ta đã chỉ ra hai cách xử lý ngoại lệ bằng cách sử dụng middleware ExceptionHandlerMiddleware và đối với hầu hết mọi người, phương pháp sử dụng PathString là lựa chọn tốt nhất. Chúng ta có thể định nghĩa đường dẫn xử lý ngoại lệ của mình bằng cách sử dụng các cách tiếp cận yêu thích của chúng ta.

Bài viết này cho thấy cách truy cập tính năng IExceptionHandlerPathFeature cho phép chúng ta truy cập vào ngoại lệ đã làm gián đoạn yêu cầu của chúng ta.

Cuối cùng, chúng ta đã sử dụng một phương thức quá tải UseExceptionHandler để định nghĩa lại đường dẫn yêu cầu của chúng ta nhằm giảm nguy cơ thất bại khi xử lý một yêu cầu không thành công.

Tôi hy vọng bạn thấy bài viết này hữu ích và vui lòng để lại nhận xét về cách bạn sử dụng middleware ExceptionHandlerMiddleware trong các ứng dụng của mình.

Nếu Comdy hữu ích và giúp bạn tiết kiệm thời gian làm việc

Bạn có thể vui lòng đưa Comdy vào whitelist của trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi trong việc trả tiền cho dịch vụ lưu trữ web để duy trì hoạt động của trang web.

ASP.NET Core
Bài Viết Liên Quan:
Cấu hình IOptions trong ASP.NET Core
Trung Nguyen 13/11/2021
Cấu hình IOptions trong ASP.NET Core

Trong bài viết này, chúng ta sẽ khám phá các bước cần thiết cần thiết để đọc cấu hình từ tập tin appsettings.json của ASP.NET Core.