در این قسمت میخواهیم یک Middleware کلی برای مدیریت خطاها در برنامه ایجاد کنیم. یعنی زمانی که خطایی در هر کجا از برنامه رخ داد بتوانیم توسط این Middleware خطا را پیدا کنیم و پیغام مناسب را به کاربر نشان دهیم. نحوه کار این Middleware به این صورت است که در اوله خط میان افزارها یا همان Middleware ها قرار میگیرد ( یعنی اگر درخواستی ارسال شود ابتدا از این میان افزار عبور میکند و در اختیار میان افزار های بعدی قرار میگیرد در نهایت بعد از این که از مابقی میان افزار ها عبور کرد نتیجه مجددا از این میان افزار قابل دسترسی است ). با این میان افزار اگر در هر کجا از برنامه ( بعد از عبور از این میان افزار ) خطایی رخ دهد میتوان خطا را catch کرد و response مناسب را به کاربر نمایش دهیم. برای پیاده سازی ابتدا یک کلاس برای نمایش متن خطا به کاربر ایجاد میکنیم.

public class ExceptionResult 
{
    public ExceptionResult(string message, int statusCode)
    {
        this.Message = message;
        this.StatusCode = statusCode;
    }
    public string Message { get; set; }
    public int StatusCode { get; set; }
}

در این کلاس متن خطا و StatusCode را به کاربر نمایش میدهیم. به عنوان مثال اگر کاربر احراز هویت نکرده باشد StatusCode با مقدار 401 به کاربر ارسال میکنیم.

سپس یک کلاس برای پیاده سازی مدیریت خطاها به نام ExceptionMiddleware ایجاد میکنیم. مانند زیر:

public class AdvancedExceptionHandler
{
    private readonly RequestDelegate _next;
    private readonly ILogger<AdvancedExceptionHandler> _logger;
    private readonly IWebHostEnvironment _env;

    public AdvancedExceptionHandler(RequestDelegate next, ILogger<AdvancedExceptionHandler> logger, IWebHostEnvironment env)
    {
        _next = next;
        _logger = logger;
        _env = env;
    }
    public async Task Invoke(HttpContext context)
    {
        string message = null;
        HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message, ex);

            if (_env.IsDevelopment())
            {
                var dic = new Dictionary<string, string>
                {
                    ["StackTrace"] = ex.StackTrace,
                    ["Exception"] = ex.Message
                };
                message = JsonConvert.SerializeObject(dic);
            }
            else
            {
                message = "an error has occurred";
            }
            await WriteToReponseAsync();
        }
        async Task WriteToReponseAsync()
        {
            if (context.Response.HasStarted)
                throw new InvalidOperationException("The response has already started");
            var exceptionResult = new ExceptionResult(message, (int)httpStatusCode);
            var result = JsonConvert.SerializeObject(exceptionResult);
            context.Response.StatusCode = (int)httpStatusCode;
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsync(result);
        }
    }
}

برای پیاده سازی Middleware شما باید یک متد به نام Invoke یا InvokeAsync ایجاد کنید که شامل یک ورودی HttpContext میباشد. در این قسمت از try/catch برای گرفتن خطاهای برنامه استفاده کرده ایم. درون بلاک try دستور 

 await _next(context);

را نوشته ایم که با این کار, درخواست از اولین میان افزار یعنی AdvancedExceptionHandler عبور میکند و به سایر میان افزارهای موجود میرسد و اگر زمانی در هرکدام از میان افزارهای بعدی خطایی رخ دهد وارد بلاک catch میشود. در این بلاک ابتدا لاگ خطا را توسط لاگر توکار دات نت کور گرفته ایم که توسط DI از طریق سازنده کلاس مقداردهی شده است. در ادامه با استفاده از کتابخانه Microsoft.Extensions.Hosting محیط اجرای برنامه را از متغییر IWebHostEnvironment گرفته ایم. که اگر در محیط production باشد فقط پیغام "an error has occurred" به کاربر نمایش داده میشود اما اگر در محیط development باشد برای نمایش اطلاعات خطا به برنامه نویس StackTrace به علاوه Message کلاس Exception را در یک دیکشنری سریالایز میکنیم و در داخل متغییر message قرار میدهیم که بعدا به کاربر نمایش داده میشود. در نهایت هم متن خطا و StatusCode را به صورت json به کاربر ارسال میکنیم. سپس باید یک اکستنشن متد برای ثبت این کلاس به pipeline دات نت کور بنویسیم.

public static class ExceptionHandlerMiddlewareExtension
{
    public static void UseAdvancedExceptionHandler(this IApplicationBuilder app)
    {
        app.UseMiddleware<AdvancedExceptionHandler>();
    }
}

و در نهایت در کلاس startup در متد Configure این اکستنشن متد را فراخوانی کنیم.

public void Configure(IApplicationBuilder app)
{
    app.UseAdvancedExceptionHandler();//<--NOTE THIS
    app.UseCookiePolicy();
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
             name: "default",
             pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

;)

Powered by Froala Editor