Я пытаюсь создать глобальный обработчик исключений для своего веб-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
и записывать ошибки в тело ответа.
МЕТОД 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();
@iKingNinja Я обновил ответ в stackoverflow и вопросах и ответах.
Я нашел решение: для веб-API на основе контроллера вам необходимо создать контроллер и добавить действие без метода для привязки UseExceptionHandler()
.
[ApiController]
[Route("error")]
public class ErrorController : Controller
{
public IActionResult Index() =>
Problem();
}
app.UseExceptionHandler("/error");
Спасибо, но этот ответ не решает мою проблему, связанную с
IExceptionHandler