.Net 6中错误处理的最佳实践

591 阅读4分钟

.Net 6中错误处理的最佳实践

有许多.NET异常处理和日志工具可以帮助开发人员将全局错误处理和日志记录整合到他们的应用程序中。

微软.NET 6快速概述

微软.NET 6是一个跨平台框架,它将.NET核心、.NET框架和Xamarin/Mono技术合并为一个框架。通过统一桌面、网络、移动和云应用程序的运行时间和SDK,延续了从.NET 5开始的统一。

与C# 10和Visual Studio 2022一起,微软在2021年11月9日普遍推出了.NET 6 。.NET 6有许多新的增强功能,包括:

  • 改进了性能
  • 配置文件--引导优化
  • 安全性改进
  • C# 10和F# 6的改进

使用Try-Catch块处理错误

Try-catch块是 处理异常 的最基本方式,使用最广泛,一般用于优雅地处理可能产生异常的代码语句。

考虑下面的ProcessFile 类作为一个代码例子:

C#

using System;
using System.IO;

public class ProcessFile
{
    public static void Main()
    {
        try
        {
            using (StreamReader sr = File.OpenText("data.txt"))
            {
                Console.WriteLine($"The first line of this file is {sr.ReadLine()}");
            }
        }
        catch (FileNotFoundException e)
        {
            Console.WriteLine($"The file was not found: '{e}'");
        }
        catch (DirectoryNotFoundException e)
        {
            Console.WriteLine($"The directory was not found: '{e}'");
        }
        catch (IOException e)
        {
            Console.WriteLine($"The file could not be opened: '{e}'");
        }
      	catch (Exception e)
        {
            Console.WriteLine($"General exception: '{e}'");
        }
    }
}

上面的代码例子是一个典型的案例,我们使用try-catch来处理异常。任何由包含在try块中的代码抛出的异常都会被catch块捕捉和处理。此外,还说明了使用捕捉多种异常类型的方法,这对于处理同一代码块的不同异常是非常有用的。

Try-catch块最好用在可能发生错误的方法中,并且你想为终端用户或服务适当地、独特地处理它。每个开发者都应该了解try-catch块,因为它是开发健壮应用程序的有用工具。然而,给每个方法都添加一个try-catch块是不可取的,会很快变得混乱。最好的办法是在有特殊需要的地方使用全局异常处理和try-catch块。

全局异常处理

全局异常处理使应用程序有更广泛的能力来处理所有的异常。这通常是通过ASP.NET核心应用程序的自定义中间件、.NET核心控制台和服务应用程序的AppDomain未处理的异常事件、或.NET第三方异常处理库来实现。

用自定义中间件处理ASP.NET核心的全局异常

使用自定义中间件来处理全局异常,可以提供更好的开发和应用体验。

考虑将下面的CustomExceptionHandlingMiddleware 类作为一个自定义中间件的例子。

C#

using System.Net;
using System.Text.Json;
using ExceptionHandling.Models.Responses;

namespace Middleware;

public class CustomExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<CustomExceptionHandlingMiddleware> _logger;

    public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<CustomExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(httpContext, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        var response = context.Response;

        var errorResponse = new ErrorResponse
        {
            Success = false
        };
        switch (exception)
        {
            case ApplicationException ex:
                if (ex.Message.Contains("Unauthorized"))
                {
                    response.StatusCode = (int) HttpStatusCode.Forbidden;
                    errorResponse.Message = ex.Message;
                    break;
                }
                response.StatusCode = (int) HttpStatusCode.BadRequest;
                errorResponse.Message = ex.Message;
                break;
            default:
                response.StatusCode = (int) HttpStatusCode.InternalServerError;
                errorResponse.Message = "Internal server error!";
                break;
        }
        _logger.LogError(exception.Message);
        var result = JsonSerializer.Serialize(errorResponse);
        await context.Response.WriteAsync(result);
    }
}

接下来,你必须在你的程序类中注册你的中间件。

C#

app.UseMiddleware<ExceptionHandlingMiddleware>();

.NET核心控制台全局异常处理

控制台应用程序中的全局异常处理是通过AppDomain未处理的异常事件完成的。在这里,你可以围绕未处理的异常发生时的情况添加自定义逻辑。

考虑一下下面的代码,它来自一个捕捉和记录异常的程序 类样本。

C#

var logger = host.Services.GetRequiredService<ILogger<Program>>();
System.AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    var exception = e.ExceptionObject as Exception;
    logger.LogError(exception, exception.Message);
}

第三方日志和异常处理

有许多.NET异常处理和日志工具可以帮助开发者将全局错误处理和日志记录整合到他们的应用程序中。这为开发者省去了很多繁重的工作,更容易实现和管理。

考虑一下ClearInsights Logging用于全局异常处理的这个例子。

ASP.NET Core

C#

using ClearInsights.Logging;
using System.Net;

var builder = WebApplication.CreateBuilder(args);

//Add to capture logs with ClearInsights Logging
builder.Logging.AddClearInsightsLogger(configuration =>
{
    configuration.ApiKey = "{ApiKey}";
    configuration.Secret = "{Environment Client Secret}";
    configuration.ApplicationName = "{Application Name}";
});


var app = builder.Build();

//Add to use ClearInsights global error handling.
//This will automatically catch and log any unhandled exceptions
app.UseClearInsightsExceptionHandling(options =>
{
    //Add to extend the error handler and add additional logic.
    //Add logic like custom HTTP response, etc...
    options.OnError += (sender, arg) =>
    {
        var response = "Oops something went wrong";
        arg.HttpContext.Response.ContentType = "text/html";
        arg.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
        arg.HttpContext.Response.WriteAsync(response);
    };
});

.NET核心

C#

using ClearInsights.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureLogging(builder =>
        builder.ClearProviders()
            //Add to capture logs with ClearInsights Logging
            .AddClearInsightsLogger(configuration =>
            {
                configuration.ApiKey = "{ApiKey}";
                configuration.Secret = "{Environment Client Secret}";
                configuration.ApplicationName = "{Application Name}";
            }))    
            .Build();

//Add to use ClearInsights global error handling.
//This will automatically catch and log any unhandled exceptions
System.AppDomain.CurrentDomain.UseClearInsightsExceptionHandling(host.Services.GetRequiredService<ILogger<Program>>());