C# .NET6 WebAPI 全局异常过滤器

自定义返回级别
namespace Demo
{
    /// <summary>
    /// 自定义返回级别
    /// </summary>
    public enum ResultLevel : int
    {
        /// <summary>
        /// 正确
        /// </summary>
        OK = 0,
        /// <summary>
        /// 警告
        /// </summary>
        Warning = 1,
        /// <summary>
        /// 异常
        /// </summary>
        Error = 2
    }
}
View Code

自定义返回代码

namespace Demo
{
    /// <summary>
    /// 自定义返回代码
    /// </summary>
    public enum ResultCode : int
    {
        /// <summary>
        /// 正确
        /// </summary>
        OK = 0,
        /// <summary>
        /// 用户名或密码错误
        /// </summary>
        LoginFailed = 1,
        /// <summary>
        /// 身份认证失败
        /// </summary>
        IdentityAuthFailed = 2,
        /// <summary>
        /// 数据模型已存在
        /// </summary>
        ModelIsExist = 101,
        /// <summary>
        /// 数据模型不存在
        /// </summary>
        ModelNoExist = 102,
        /// <summary>
        /// 数据模型错误
        /// </summary>
        ModelError = 103,
        /// <summary>
        /// 文件校验失败
        /// </summary>
        FileError = 104,
        /// <summary>
        /// MQTT回执超时
        /// </summary>
        MqttRecriptTimeout = 105,
        /// <summary>
        /// MQTT回执结果失败
        /// </summary>
        MqttRecriptResultFail = 106,
        /// <summary>
        /// HTTP请求失败
        /// </summary>
        HttpRequestFail = 107,
        /// <summary>
        /// 请求内容类型错误
        /// </summary>
        ContentTypeError = 996,
        /// <summary>
        /// 用户信息异常
        /// </summary>
        UserInfoError = 997,
        /// <summary>
        /// 系统异常
        /// </summary>
        LogicError = 998,
        /// <summary>
        /// 系统异常
        /// </summary>
        OtherError = 999
    }
}
View Code

自定义返回信息

namespace Demo
{
    /// <summary>
    /// 自定义返回信息
    /// </summary>
    public class ResultMsg
    {
#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释
        public const string OK = "ok";
        public const string LoginFailed = "用户名或密码错误";
        public const string IdentityAuthFailed = "身份认证失败";
        public const string ModelIsExist = "数据模型已存在";
        public const string ModelNoExist = "数据模型不存在";
        public const string ModelError = "数据模型错误";
        public const string FileError = "文件校验失败";
        public const string MqttRecriptTimeout = "等待MQTT回执超时";
        public const string ContentTypeError = "请求内容类型错误";
        public const string UserInfoError = "用户信息异常";
        public const string LogicError = "业务逻辑异常";
        public const string OtherError = "系统内部异常";
        public const string HttpRequestFail = "HTTP请求失败:{0}";
#pragma warning restore CS1591 // 缺少对公共可见类型或成员的 XML 注释
    }
}
View Code

自定义返回结构

namespace Demo
{
    /// <summary>
    /// 自定义返回结构
    /// </summary>
    public class CustomResult<T>
    {
        /// <summary>
        /// 返回码
        /// </summary>
        public string? Code { get; set; }
        /// <summary>
        /// 返回信息
        /// </summary>
        public string? Msg { get; set; } = ResultMsg.OK;
        /// <summary>
        /// 返回数据
        /// </summary>
        public T? Data { get; set; }
        /// <summary>
        /// 构造函数
        /// </summary>
        public CustomResult()
        {
            Code = ((int)ResultLevel.OK).ToString() + ((int)ResultCode.OK).ToString("D3");
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="t"></param>
        public CustomResult(T? t)
        {
            if (t is CustomException cex)
            {
                Code = ((int)cex.Level).ToString() + ((int)cex.Code).ToString("D3");
                Msg = cex.Message;
            }
            else if (t is Exception)
            {
                Code = ((int)ResultLevel.Error).ToString() + ((int)ResultCode.OtherError).ToString("D3");
                Msg = ResultMsg.OtherError;
            }
            else
            {
                Code = ((int)ResultLevel.OK).ToString() + ((int)ResultCode.OK).ToString("D3");
                Data = t;
            }
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="level"></param>
        /// <param name="code"></param>
        /// <param name="t"></param>
        public CustomResult(ResultLevel level, ResultCode code, T t)
        {
            Code = ((int)level).ToString() + ((int)code).ToString("D3");
            Data = t;
        }
    }
}
View Code

自定义异常类

namespace Demo
{
    /// <summary>
    /// 自定义异常类
    /// </summary>
    [Serializable]
    public class CustomException : Exception
    {
        /// <summary>
        /// 错误级别
        /// </summary>
        public ResultLevel Level { get; set; } = ResultLevel.Error;
        /// <summary>
        /// 错误码
        /// </summary>
        public ResultCode Code { get; set; } = ResultCode.OtherError;
        /// <summary>
        /// 构造函数
        /// </summary>
        public CustomException()
        {
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="message"></param>
        public CustomException(string? message) : base(message)
        {
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="errLevel"></param>
        /// <param name="errCode"></param>
        /// <param name="message"></param>
        public CustomException(ResultLevel errLevel, ResultCode errCode, string? message) : base(message)
        {
            Level = errLevel;
            Code = errCode;
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="errLevel"></param>
        /// <param name="errCode"></param>
        public CustomException(ResultLevel errLevel, ResultCode errCode) : base()
        {
            Level = errLevel;
            Code = errCode;
        }
    }
}
View Code

自定义异常过滤器(控制器全局异常过滤器)

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Demo
{
    /// <summary>
    /// 自定义异常过滤器(控制器异常)
    /// </summary>
    public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
    {
        /// <summary>
        /// 日志
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger"></param>
        public CustomExceptionFilterAttribute(ILogger<CustomExceptionFilterAttribute> logger)
        {
            _logger = logger;
        }
        /// <summary>
        /// 发生异常事件
        /// </summary>
        /// <param name="context"></param>
        public override void OnException(ExceptionContext context)
        {
            // 自定义异常
            if (context.Exception is CustomException ex)
            {
                _logger.LogError(ex, ResultMsg.LogicError);
                var result = new CustomResult<CustomException>(ex);
                context.Result = new BadRequestObjectResult(result);
            }
            // 系统异常
            else
            {
                _logger.LogError(context.Exception, ResultMsg.OtherError);
                var result = new CustomResult<Exception>(context.Exception);
                context.Result = new ObjectResult(result) { StatusCode = StatusCodes.Status500InternalServerError };
            }
            // 标记异常已处理
            context.ExceptionHandled = true;
        }
        /// <summary>
        /// 发生异常事件
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task OnExceptionAsync(ExceptionContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            OnException(context);
            return Task.CompletedTask;
        }
    }
}
View Code

自定义扩展类

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Text.Json;

namespace Demo
{
    /// <summary>
    /// 自定义扩展类
    /// </summary>
    public static class CustomExpand
    {
        /// <summary>
        /// 添加模型绑定异常处理
        /// </summary>
        /// <param name="services"></param>
        /// <exception cref="CustomException"></exception>
        public static void AddModelBindingExceptionHandling(this IServiceCollection services)
        {
            services.Configure<ApiBehaviorOptions>(options =>
            {
                options.InvalidModelStateResponseFactory = actionContext =>
                {
                    // 获取验证失败的模型字段
                    //var errors = actionContext.ModelState
                    //    .Where(s => s.Value != null && s.Value.ValidationState == ModelValidationState.Invalid)
                    //    .SelectMany(s => s.Value!.Errors.ToList())
                    //    .Select(e => e.ErrorMessage)
                    //    .ToList();
                    var error = actionContext.ModelState
                        .Where(s => s.Value != null && s.Value.ValidationState == ModelValidationState.Invalid)
                        .SelectMany(s => s.Value!.Errors.ToList())
                        .Select(e => e.ErrorMessage).FirstOrDefault();
                    // 统一返回格式
                    throw new CustomException(ResultLevel.Error, ResultCode.ModelError, error);
                    //var result = new CustomResult<List<string>>(ResultLevel.Error, ResultCode.ModelError, errors);
                    //return new BadRequestObjectResult(result);
                };
            });
        }
        /// <summary>
        /// 添加身份认证事件
        /// </summary>
        /// <param name="options"></param>
        /// <returns></returns>
        public static JwtBearerOptions AddAuthenticationEvents(this JwtBearerOptions options)
        {
            // JWT
            options.Events = new JwtBearerEvents()
            {
                // 未登录
                OnChallenge = context => AuthenticationFailed(context),
                // 身份认证失败
                OnAuthenticationFailed = context => AuthenticationFailed(context),
                // 没有权限
                OnForbidden = context => AuthenticationFailed(context),
            };
            return options;
        }
        /// <summary>
        /// 身份认证失败
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private static Task AuthenticationFailed(BaseContext<JwtBearerOptions> context)
        {
            var ex = new CustomException(ResultLevel.Error, ResultCode.IdentityAuthFailed, ResultMsg.IdentityAuthFailed);
            var result = new CustomResult<CustomException>(ex);
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            context.Response.ContentType = "application/json";
            context.Response.Body.Flush();
            context.Response.Body.Position = 0;
            return JsonSerializer.SerializeAsync(context.Response.Body, result,
                new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }
            );
        }
    }
}
View Code

自定义异常处理中间件

using System.Text.Json;

namespace Demo
{
    /// <summary>
    /// 自定义异常处理中间件
    /// </summary>
    public class ExceptionMiddleware
    {
        /// <summary>
        /// 请求委托
        /// </summary>
        private readonly RequestDelegate _next;
        /// <summary>
        /// 日志
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="next"></param>
        /// <param name="logger"></param>
        public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }
        /// <summary>
        /// 异步调用
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context)
        {
            try
            {
                // 向下执行(等待返回)
                await _next(context);
            }
            catch (Exception ex)
            {
                // 日志
                _logger.LogError(ex, ResultMsg.OtherError);
                // 响应信息
                var result = new CustomResult<Exception>(ex);
                context.Response.ContentType = "application/json; charset=utf-8";
                context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                var stream = context.Response.Body;
                await JsonSerializer.SerializeAsync(stream, result,
                    new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }
                );
            }
        }
    }
}
View Code

使用方法:Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Serilog;
using Serilog.Events;
using System.Reflection;
using System.Text;

// 创建应用程序
var builder = WebApplication.CreateBuilder(args);
// 创建Serilog
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .CreateBootstrapLogger();
try
{
    Log.Information("Starting web host");

    // 配置文件
    var configuration = builder.Configuration;
    // 配置Serilog
    builder.Host.UseSerilog((context, services, configuration) => configuration
        .ReadFrom.Configuration(context.Configuration)
        .ReadFrom.Services(services));

    // 向容器中添加服务。
    // 添加控制器
    builder.Services.AddControllers(options =>
    {
        // 自定义异常过滤器(仅对控制器级别的错误进行处理)
        options.Filters.Add(typeof(CustomExceptionFilterAttribute));
    })
        // 响应数据的格式配置
        .AddNewtonsoftJson(options =>
        {
            options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
            options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
        });
    // 添加JWT身份验证服务
    builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
        // Jwt验证配置
        .AddJwtBearer(options =>
        {
            // 身份认证参数
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = configuration["Jwt:Issuer"],
                ValidAudience = configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:IssuerSigningKey"]))
            };
            // 身份认证事件
            options.AddAuthenticationEvents();
        });
    // 添加模型绑定异常处理
    builder.Services.AddModelBindingExceptionHandling();
    // 生成应用程序
    var app = builder.Build();

    // 配置HTTP请求管道。
    // 开发者模式
    if (app.Environment.IsDevelopment())
    {
        // 使用开发者异常页面 
        app.UseDeveloperExceptionPage();
    }
    // 使用Swagger
    app.UseCustomSwagger(configuration);
    // 使用Serilog记录Request
    app.UseSerilogRequestLogging();
    // 使用WebSocket
    app.UseWebSockets();
    // 使用自定义中间件(异常处理中间件)
    app.UseMiddleware<ExceptionMiddleware>();
    // 使用HTTPS重定向
    app.UseHttpsRedirection();
    // 使用身份认证
    app.UseAuthentication();
    // 使用授权
    app.UseAuthorization();
    // 映射控制器
    app.MapControllers();
    // 运行应用程序
    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
    Log.CloseAndFlush();
}

 

posted @ 2024-04-02 10:16  Mr_Xul  阅读(220)  评论(0编辑  收藏  举报