Chapter5:GLOBAL ERROR HANDLING

5 GLOBAL ERROR HANDLING

Exception handling helps us deal with the unexpected behavior of our system. To handle exceptions, we use the try-catch block in our code as well as the finally keyword to clean up our resources afterwards.
异常处理有助于我们处理系统的意外行为。为了处理异常,我们在代码中使用 try-catch 块以及 finally 关键字来清理资源。

Even though there is nothing wrong with the try-catch blocks in our Actions in the Web API project, we can extract all the exception handling logic into a single centralized place. By doing that, we make our actions cleaner, more readable, and the error handling process more maintainable.
尽管 Web API 项目中的操作中的 try-catch 块没有错,但我们可以将所有异常处理逻辑提取到一个集中的位置。通过这样做,我们使我们的操作更干净,更具可读性,并且错误处理过程更易于维护。

In this chapter, we are going to refactor our code to use the built-in middleware and our custom middleware for global error handling to demonstrate the benefits of this approach.
在本章中,我们将重构代码,以使用内置中间件和自定义中间件进行全局错误处理,以演示此方法的优势。

5.1 Handing Errors Globally with Built-In Middleware

The UseExceptionHandler middleware is a built-in middleware that we can use to handle exceptions. So, let’s dive into the code to see this middleware in action.
UseExceptionHandler 中间件是一个内置的中间件,我们可以用来处理异常。因此,让我们深入研究代码,看看这个中间件的实际效果。

We are going to create a new ErrorModel folder in the Entities project, and add the new class ErrorDetails in that folder:
我们将在Entities项目中创建一个新的 ErrorModel 文件夹项目,并在该文件夹中添加新类 ErrorDetails

using System.Text.Json;
namespace Entities.ErrorModel
{
    public class ErrorDetails 
    { 
        public int StatusCode { get; set; } 
        public string Message { get; set; } 
        public override string ToString() => JsonSerializer.Serialize(this);
    }
}

We are going to use this class for the details of our error message.
我们将使用此类来了解错误消息的详细信息。

To continue, in the Extensions folder in the main project, we are going to add a new static class: ExceptionMiddlewareExtensions.cs.
为了继续,在主项目的扩展文件夹中,我们将添加一个新的静态类:ExceptionMiddlewareExtensions.cs

Now, we need to modify it:
现在,我们需要修改它:

using Contracts;
using Entities.ErrorModel;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using System.Net;

namespace CompanyEmployees.Extensions
{
    public static class ExceptionMiddlewareExtensions {
        public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILoggerManager logger) 
        { 
            app.UseExceptionHandler(appError => 
            { 
                appError.Run(async context => {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 
                    context.Response.ContentType = "application/json"; 
                    var contextFeature = context.Features.Get<IExceptionHandlerFeature>(); 
                    if (contextFeature != null) 
                    { 
                        logger.LogError($"Something went wrong: {contextFeature.Error}"); 
                        await context.Response.WriteAsync(new ErrorDetails() 
                        { 
                              StatusCode = context.Response.StatusCode, 
                              Message = "Internal Server Error." 
                              }.ToString()); 
                    } 
                }); 
            }); 
        } 
    }
}

In the code above, we’ve created an extension method in which we’ve registered the UseExceptionHandler middleware. Then, we’ve populated the status code and the content type of our response, logged the error message, and finally returned the response with the custom created object.
在上面的代码中,我们创建了一个扩展方法,在该方法中注册了 UseExceptionHandler 中间件。然后,我们填充了响应的状态代码和内容类型,记录了错误消息,最后返回了带有自定义创建对象的响应。

5.2 Starpup Class Modification
To be able to use this extension method, let’s modify the Configure method inside the Startup class:
为了能够使用此扩展方法,让我们修改Configure Startup 类中的方法:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerManager logger)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.ConfigureExceptionHandler(logger);
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseCors("CorsPolicy");
            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.All
            });

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

Finally, let’s remove the try-catch block from our code:
最后,让我们从代码中删除 try-catch 块:

using AutoMapper;
using Contracts;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace CompanyEmployees.Controllers
{
    [Route("api/companies")]
    [ApiController]
    public class CompaniesController : ControllerBase
    {
        private readonly IRepositoryManager _repository; 
        private readonly ILoggerManager _logger; 
        private readonly IMapper _mapper; 
        public CompaniesController(IRepositoryManager repository, ILoggerManager logger, IMapper mapper) 
        { 
            _repository = repository; 
            _logger = logger; 
            _mapper = mapper;
        }

        [HttpGet]
        public IActionResult GetCompanies()
        {
            //try
            //{
            //    var companies = _repository.Company.GetAllCompanies(trackChanges: false);
            //    var companiesDto = _mapper.Map<IEnumerable<CompanyDto>>(companies);
            //    return Ok(companiesDto);
            //}
            //catch (Exception ex)
            //{
            //    _logger.LogError($"Something went wrong in the {nameof(GetCompanies)} action {ex}");
            //    return StatusCode(500, "Internal server error");
            //}

            var companies = _repository.Company.GetAllCompanies(trackChanges: false); 
            var companiesDto = _mapper.Map<IEnumerable<CompanyDto>>(companies); 
            return Ok(companiesDto);
        }
    }
}

And there we go. Our action method is much cleaner now. More importantly, we can reuse this functionality to write more readable actions in the future.
我们就这样吧。我们的行动方法现在更干净了。更重要的是,我们可以重用此功能,以便在将来编写更具可读性的操作。

5.3 Testing the Result

To inspect this functionality, let’s add the following line to the GetCompanies action, just to simulate an error:
要检查此功能,让我们将以下行添加到GetCompanies 操作,只是为了模拟错误:

throw new Exception("Exception");

And send a request from Postman:
并发送邮递员的请求:
https://localhost:5001/api/companies

alt

We can check our log messages to make sure that logging is working as well.
我们可以检查日志消息,以确保日志记录也正常工作。

发表评论