Пользовательский IExceptionHandler ASP.NET Core не перехватывает исключения

Я пытаюсь создать глобальный обработчик исключений для своего веб-API, используя IExceptionHandler. Однако мой обработчик не перехватывает никаких исключений.

public class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{       
    public ValueTask<bool> TryHandleAsync(HttpContext context, Exception ex, CancellationToken ct)
    {
        logger.LogError(ex, "Controller error occured");

        ErrorResponse errors = new()
        {
            Errors = [new ApiError(-1, "Internal Server Error")]
        };
        string jsonRes = JsonSerializer.Serialize(errors);

        context.Response.BodyWriter.Write(Encoding.UTF8.GetBytes(jsonRes));

        return ValueTask.FromResult(true);
    }
}

Программа.cs

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
using Asp.Versioning;
using API.Services;
using NRediSearch;
using StackExchange.Redis;
using API.Authentication.ApiKeyAuthenticaiton;
using Microsoft.OpenApi.Models;
using API;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEnvironmentVariables();

IConfiguration configuration = builder.Configuration;

// Add services to the container.

builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1);
    options.ReportApiVersions = true;
    options.AssumeDefaultVersionWhenUnspecified = false;
    options.ApiVersionReader = ApiVersionReader.Combine(
        new UrlSegmentApiVersionReader());
}).AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'V";
    options.SubstituteApiVersionInUrl = true;
});

builder.Services.AddSwaggerGen(options =>
{
    options.EnableAnnotations();
    options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
    {
        Title = "API",
        Version = "v1"
    });

    options.AddSecurityDefinition(ApiKeyAuthenticationDefaults.AuthenticationScheme, new()
    {
        Name = "x-api-key",
        Description = "API key authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey
    });

    options.AddSecurityRequirement(new()
    {
        {
            new OpenApiSecurityScheme()
            {
                Reference = new OpenApiReference()
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = ApiKeyAuthenticationDefaults.AuthenticationScheme
                },
                Name = ApiKeyAuthenticationDefaults.AuthenticationScheme,
                In = ParameterLocation.Header
            },
            new List<string>()
        }
    });
});

builder.Services.Configure<RouteOptions>(options =>
{
    options.LowercaseUrls = true;
});

string redisConnectionString = builder.Configuration.GetConnectionString("Redis") ?? throw new Exception("No Redis connection string found");

builder.Services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(redisConnectionString));
builder.Services.AddSingleton(serviceProvider =>
{
    IConnectionMultiplexer multiplexer = serviceProvider.GetRequiredService<IConnectionMultiplexer>();
    IDatabase db = multiplexer.GetDatabase();
    Client client = new("queueIdx", db);

    return client;
});
builder.Services.AddSingleton<IRedisService, RedisService>();
builder.Services.AddScoped<IQueueManager, QueueManager>();
builder.Services.AddSingleton<QueuesPoolService>();
builder.Services.AddSingleton(configuration);
builder.Logging.AddConsole();

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = ApiKeyAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = ApiKeyAuthenticationDefaults.AuthenticationScheme;
})
    .AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationDefaults.AuthenticationScheme, options =>
    {
        options.ApiKey = configuration["ApiKey"] ?? throw new Exception("No API key was configured");
    });

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

// Set up Redis DB

IRedisService redis = app.Services.GetRequiredService<IRedisService>();
await redis.CreateIndexAsync();
await redis.ConfigureAsync();

app.MapControllers();

app.Run();

Я намеренно выдаю ошибку из-за действия контроллера, и она не обнаруживается. Точка останова отладчика вообще не срабатывает.

public async Task<IActionResult> GetCurrentPlayerQueue(long userId)
{
    throw new Exception("test");
}

API следует поведению обработки исключений по умолчанию: он возвращает полную информацию об исключениях в среде разработки и пустой текст ответа в рабочей среде. Я ожидаю, что мой API будет использовать GlobalExceptionHandler и записывать ошибки в тело ответа.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

МЕТОД 1 — Обработчик исключений

Измените свой GlobalExceptionHandler, как показано ниже, и используйте ProblemDetails.

using Microsoft.AspNetCore.Diagnostics;
using System.Buffers;
using System.Text.Json;
using System.Text;
using Microsoft.AspNetCore.Mvc;

namespace WebApplication1
{
    public class GlobalExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<GlobalExceptionHandler> _logger;

        public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
        {
            _logger = logger;
        }

        public async ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            _logger.LogError(
                exception, "Exception occurred: {Message}", exception.Message);

            var problemDetails = new ProblemDetails
            {
                Status = StatusCodes.Status500InternalServerError,
                Title = "Server error"
            };

            httpContext.Response.StatusCode = problemDetails.Status.Value;

            await httpContext.Response
                .WriteAsJsonAsync(problemDetails, cancellationToken);

            return true;
        }
    }
}

И зарегистрируйте его, как показано ниже.

...
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
// Add this Line
builder.Services.AddProblemDetails();

var app = builder.Build();
// Add this line
app.UseExceptionHandler();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
...

МЕТОД 2 — Промежуточное программное обеспечение

Мы можем создать собственное промежуточное программное обеспечение для захвата глобального исключения.

GlobalExceptionHandlerMiddleware.cs

using System.Text.Json;
namespace WebApplication1
{
    public class GlobalExceptionHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger;
        public GlobalExceptionHandlerMiddleware(RequestDelegate next, ILogger<GlobalExceptionHandlerMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }
        private Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            _logger.LogError(exception, "An unhandled exception occurred.");
            var errorResponse = new
            {
                Errors = new[] { new { Code = -1, Message = "Internal Server Error" } }
            };
            var result = JsonSerializer.Serialize(errorResponse);
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            return context.Response.WriteAsync(result);
        }
    }
}

Программа.cs

using WebApplication1;

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//builder.Services.AddExceptionHandler<GlobalExceptionHandler>();

var app = builder.Build();
// Tip
// Register it in the top, it can capture all the exceptions
app.UseMiddleware<GlobalExceptionHandlerMiddleware>();


// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

Спасибо, но этот ответ не решает мою проблему, связанную с IExceptionHandler

iKingNinja 29.08.2024 20:27

@iKingNinja Я обновил ответ в stackoverflow и вопросах и ответах.

Jason Pan 30.08.2024 08:26
Ответ принят как подходящий

Я нашел решение: для веб-API на основе контроллера вам необходимо создать контроллер и добавить действие без метода для привязки UseExceptionHandler().

[ApiController]
[Route("error")]
public class ErrorController : Controller
{
    public IActionResult Index() =>
        Problem();
}
app.UseExceptionHandler("/error");

Источник: https://learn.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-8.0#Exception-handler

Другие вопросы по теме