如何用.NET Core玩转全局异常,让程序崩溃也笑出泪花!!!

引言

1.异常处理的重要性

  • 异常处理是任何应用程序中不可或缺的一部分,它有助于确保应用程序的稳定性、可靠性和安全性。在发生错误或异常情况时,异常处理机制能够捕获并适当处理这些错误,以防止程序崩溃,并向用户提供有关错误的反馈。
  • 异常处理可以提高用户体验,因为当用户遇到问题时,应用程序可以提供有关错误的清晰信息,而不是简单地崩溃。这有助于用户理解和解决问题,并提高对应用程序的信任度。
  • 异常处理还为开发人员提供了诊断和调试应用程序的工具。通过查看异常信息,开发人员可以确定问题的根源,并采取适当的措施来解决它。这有助于减少开发时间和成本,并提高应用程序的质量。

2.NET Core中的异常处理机制

  • .NET Core 提供了强大的异常处理机制,包括异常的抛出、捕获和传播。在 .NET Core 中,异常通常通过使用 try-catch 块来捕获。当 try 块中的代码引发异常时,控制流将立即转移到相应的 catch 块中,以便处理异常
  • .NET Core 还支持异常的层次结构,这意味着可以根据需要捕获特定类型的异常或其子类。这使得开发人员能够根据需要定制异常处理逻辑,例如只处理特定类型的异常或将其记录到日志中。
  • 异常的传播是指异常从引发它的位置向上层调用堆栈传播的过程。在 .NET Core 中,如果一个方法引发了异常,并且该异常没有被该方法内部的 catch 块捕获,那么该异常将被传播到调用该方法的方法中。这个过程将继续进行,直到找到一个 catch 块来捕获该异常,或者直到该异常到达应用程序的入口点。

全局异常处理的几种方式

1.使用异常过滤器(Exception Filters)

  • 定义异常过滤器:在.NET 6及更高版本中,可以使用异常过滤器来简化异常处理逻辑。异常过滤器是使用ExceptionFilterAttribute特性标记的类,这些类必须实现IExceptionFilter接口。通过实现OnException方法,可以在方法执行期间捕获异常。
  • 使用 catch 关键字捕获异常:在异常过滤器中,可以使用 catch 块来捕获异常。通过检查异常的类型和信息,可以决定是否要处理该异常或将其传递给下一个过滤器或控制器
  • 过滤器在 ASP.NET Core 中的使用:要将异常过滤器应用于ASP.NET Core应用程序,需要在启动类或配置文件中注册它们。注册后,它们将自动应用于所有控制器和动作方法
    以下是自定义异常类CustomExceptionFilterProgram.cs中注册 相关例子实现代码:
 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
 public class CustomExceptionFilter : ExceptionFilterAttribute, IExceptionFilter
 {
     public override void OnException(ExceptionContext context)
     {
         // 捕获并处理所有在控制器操作方法执行期间抛出的异常
         var exception = context.Exception;
         // 这里可以自定义判断异常处理
         if (exception is NotImplementedException)
         {
             // 处理特定类型的异常
             context.Result = new JsonResult(new { error = "未实现的功能" });
             context.HttpContext.Response.StatusCode = StatusCodes.Status501NotImplemented;
         }
         else if (context.ModelState.IsValid == false)
         {
             // 模型验证错误与特定异常结合的情况,可以返回更友好的错误消息
             context.Result = new BadRequestObjectResult(context.ModelState);
         }
         else if (exception is ArgumentNullException)
         {
             // 参数为null或者为空
             context.Result = new JsonResult(new { message = "请求参数无效,请检查后重新提交。" });
         }
         else
         {
             // 其他未特别处理的异常,可以选择记录日志,是否应传递给下一个过滤器或控制器
             // 这里记录日志
             Console.WriteLine($"异常信息:{exception.Message}");
             context.ExceptionHandled = true; // 标记该异常已在此处处理
         }

         base.OnException(context);
     }
 }
// 注册服务
builder.Services.AddControllers(options =>
{
    // 全局应用异常过滤器
    options.Filters.Add(typeof(CustomExceptionFilter));
});

2.使用 IActionFilter、IExceptionFilter 和 IResultFilter 接口

  • 实现接口定义过滤器:通过实现IActionFilter、IExceptionFilter 和 IResultFilter接口,可以创建自定义的过滤器类。这些接口提供了在动作执行之前、之后或发生异常时执行的方法。
  • 在过滤器中处理异常:在实现 IExceptionFilter 接口的类中,可以编写代码来处理异常。当控制器中的动作抛出异常时,该过滤器的 OnException 方法将被调用。
  • 在 ASP.NET Core 中的使用:通过将自定义过滤器添加到控制器或动作方法上,或者通过在全局范围内注册它们,可以将过滤器应用于ASP.NET Core应用程序的特定部分或所有部分。
    以下是使用实现接口定义过滤器``和Program.cs中注册 相关例子`实现相关代码:
// 定义实现接口过滤器
public class CustomFilters : Attribute, IActionFilter, IExceptionFilter, IResultFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        //方法执行之前调用
        Console.WriteLine($"[CustomFilters] OnActionExecuting: {context.ActionDescriptor.DisplayName}");
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        //方法执行之后调用
        Console.WriteLine($"[CustomFilters] OnActionExecuted: {context.ActionDescriptor.DisplayName}");
    }

  
    public void OnException(ExceptionContext context)
    {
        //方法执行期间发生异常时调用
        Console.WriteLine($"[CustomFilters] Exception caught: {context.Exception.Message}");

        //自定义判断处理
        //比如
        if (context.Exception is ArgumentNullException)
        {
            context.Result = new JsonResult(new { message = "参数不能为空,请检查后重新提交。" })
            {
                StatusCode = StatusCodes.Status400BadRequest
            };
            context.ExceptionHandled = true; // 标记异常已处理,后续处理器不再处理该异常
        }
    }


    public void OnResultExecuting(ResultExecutingContext context)
    {
        // 返回结果执行之前调用
        Console.WriteLine($"[CustomFilters] OnResultExecuting: {context.ActionDescriptor.DisplayName}");
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // 返回结果执行之后调用
        Console.WriteLine($"[CustomFilters] OnResultExecuted: {context.ActionDescriptor.DisplayName}");
    }


}
// 启动时注册
// 注册服务
builder.Services.AddControllers(options =>
{
    // 注册过滤器
    options.Filters.Add();
});

3.使用异常中间件(Exception Middleware)

  • 创建自定义中间件中间件ASP.NET Core应用程序中处理请求和响应的组件。通过创建自定义中间件,您可以捕获由请求管道中的其他组件引发的异常。
  • 在中间件中捕获异常中间件可以访问请求和响应对象,并在处理过程中检查是否有异常发生。如果有异常,中间件可以捕获它们并执行自定义逻辑,例如记录日志或将用户重定向到错误页面。
  • 中间件在 ASP.NET Core 中的注册和使用:要将自定义中间件添加到ASP.NET Core应用程序,需要在启动类中的Configure方法中注册它。注册后,中间件将自动应用于所有请求管道
    以下实现自定义中间件 处理异常和Program.cs中注册 相关`例子:
public class CustomExceptionHandlerMiddleware
{
    // 定义个请求委托,存储下一个中间件的引用
    private readonly RequestDelegate _next;

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


    public async Task InvokeAsync(HttpContext context)
    {
        //处理HTTP请求
        try
        {
            //context当前的HTTP请求上下文
            await _next(context);
        }
        catch (Exception ex)
        {
            // 在这里捕获并处理异常
            //可以把异常写入到日志
            Console.WriteLine(ex.Message, "An unhandled exception occurred");

            // 设置响应状态码
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

            // 根据异常类型返回不同的错误信息或视图
            if (ex is ArgumentException)
            {
                await context.Response.WriteAsJsonAsync(new { error = "参数无效" });
            }
            else
            {
                await context.Response.WriteAsync("发生内部服务器错误,请稍后重试");
            }
        }
    }
}
//注册中间件
app.UseMiddleware();

4.使用日志框架记录异常信息

  • 选择合适的日志框架(如 Serilog、NLog 等):在.NET 6及更高版本中,有多个流行的日志框架可用,如Serilog和NLog。选择一个适合您应用程序需求的日志框架非常重要。
  • 在日志框架中配置异常日志记录:在所选日志框架的配置中,可以指定将异常信息记录到指定的目标(如文件、数据库或远程日志系统)。配置通常涉及指定记录级别、格式化器和目标位置等设置。
  • 日志框架在 .NET Core 中的使用:使用所选日志框架记录异常的基本通常涉及捕获异常并将其实例传递给日志框架的方法或属性。然后由日志框架负责将信息记录到您定义的目标位置。
    以下以NLog实现相关例子:首先需要安装NLogNLog.Web.AspNetCore
// Program.cs 中配置
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
try
{
	//配置Nlog
	builder.Logging.ClearProviders();
	builder.Logging.SetMinimumLevel(LogLevel.Trace);
	builder.Host.UseNLog();
}
catch (Exception ex)
{
    logger.Error(ex, "Stopped program because of exception");
    throw;
}
finally
{
    // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
    LogManager.Shutdown();
}
// nlog 配置文件

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Info">
  
  <extensions>
    <add assembly="NLog.Web.AspNetCore" />
  extensions>
  
  <targets>
    
    <target xsi:type="File" name="debug" fileName="./logs/debug-${shortdate}.log" layout="【记录时间】:${longdate}${newline}【日志级别】:${uppercase:${level}}${newline}【请求地址】: ${aspnet-request-url}${newline}【方 法 名】: ${aspnet-mvc-action}${newline}【日志信息】:${message}${newline}${exception:format=tostring}${newline}" />

    
    <target xsi:type="File" name="info" fileName="./logs/info-${shortdate}.log" layout="【记录时间】:${longdate}${newline}【日志级别】:${uppercase:${level}}${newline}【请求地址】: ${aspnet-request-url}${newline}【方 法 名】: ${aspnet-mvc-action}${newline}【日志信息】:${message}${newline}${exception:format=tostring}${newline}" />

    
    <target xsi:type="File" name="warn" fileName="./logs/warn-${shortdate}.log" layout="【记录时间】:${longdate}${newline}【日志级别】:${uppercase:${level}}${newline}【请求地址】: ${aspnet-request-url}${newline}【方 法 名】: ${aspnet-mvc-action}${newline}【日志信息】:${message}${newline}${exception:format=tostring}${newline}" />
    
    <target xsi:type="File" name="error" fileName="./logs/error-${shortdate}.log"   layout="【记录时间】:${longdate}${newline}【日志级别】:${uppercase:${level}}${newline}【请求地址】: ${aspnet-request-url}${newline}【方 法 名】: ${aspnet-mvc-action}${newline}【日志信息】:${message}${newline}${exception:format=tostring}${newline}" />

    
    <target xsi:type="File" name="task" fileName="./logs/task-${shortdate}.log" layout="【记录时间】:${longdate}${newline}【日志级别】:${uppercase:${level}}${newline}【请求地址】: ${aspnet-request-url}${newline}【方 法 名】: ${aspnet-mvc-action}${newline}【日志信息】:${message}${newline}${exception:format=tostring}${newline}" />

    
    <target xsi:type="Console" name="console" layout="${message}" />
  targets>
  
  <rules>
    
    
    <logger name="*" levels="Warn" writeTo="warn" />
    
    
    <logger name="*" minlevel="Error" maxlevel="Fatal" writeTo="error" />
    
    <logger name="Microsoft.*" maxlevel="Info" final="true" />
  rules>
nlog>

5.自定义异常处理机制

  • 自定义异常类和异常处理逻辑:您可以定义自己的异常类来封装特定的错误信息或状态,并为它们编写相应的处理逻辑。这可以通过创建派生自内置异常类的自定义异常类来实现。
  • 在应用程序中全局应用自定义异常处理逻辑:要全局应用自定义的异常处理逻辑,您可以在应用程序的入口点(例如主函数或程序启动类)中添加代码来捕获并处理自定义异常。这可以在全局范围内处理特定类型的异常,以确保它们得到适当的处理和响应。
    以下是实现自定义异常类全局处理类以及启动时候注册相关代码例子
// 继承Exception自定义类
public class CustomException : Exception  
{  
    public CustomException(string message) : base(message) { }  
}

// 创建一个全局异常处理类
public class GlobalExceptionHandler  
{  
    public static void RegisterGlobalExceptionHandler()  
    {  
        // 注册全局异常处理程序  
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);  
    }  
  
    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)  
    {  
        // 处理异常逻辑  
        var exception = e.ExceptionObject as CustomException;  
        if (exception != null)  
        {  
            // 处理自定义异常的逻辑  
            //这里可以返回相关提示给前端
        }  
        else  
        {  
            // 处理其他异常的逻辑  
           //这里可以处理,写入相关日志
           
        }  
    }  
}

//最后在Program.cs中注册自定义异常处理类
// 注册全局异常处理程序 
GlobalExceptionHandler.RegisterGlobalExceptionHandler();

公众号“点滴分享技术猿

关注

你可能感兴趣的:(.netcore,服务器,java)