Я пытаюсь реализовать глобальную обработку исключений в своем приложении ASP.NET Core. Моя цель — перехватить все необработанные исключения и вернуть стандартизированный ответ об ошибке, используя объект ProblemDetails
.
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger _logger = Log.ForContext(typeof(GlobalExceptionHandler));
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.Error(exception, "Global Exception Handler");
var problemDetails = new ProblemDetails
{
Status = (int)HttpStatusCode.InternalServerError,
Title = "An exception occurred",
Detail = exception.Message
};
httpContext.Response.StatusCode = problemDetails.Status.Value;
httpContext.Response.ContentType = "application/problem+json";
await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
return true;
}
}
Проблема: код состояния HTTP правильно установлен на 500
, но объект ProblemDetails
не возвращается в теле ответа. Вместо этого тело ответа пусто, и клиент не получает ожидаемые сведения об ошибке JSON.
Почему объект ProblemDetails
не возвращается в теле ответа и как я могу гарантировать, что объект ProblemDetails
правильно сериализован и возвращен?
Заранее спасибо!
Убедитесь, что при запуске вы зарегистрировали все необходимые службы и настроили промежуточное программное обеспечение:
// adding needed services
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
// ...
// Register middleware
app.UseExceptionHandler();
После их добавления ваш пример работал как шарм :)
Может быть, вы применяете их условно? Не могли бы вы поделиться кодом, где вы все прописываете?
Итак, я понял, что метод WriteAsJsonAsync ничего не записывает в ответ. Он выполняется, но даже если я напишу простую строку вместо переменной проблемаДеталис, на вкладке ответа сетевого вызова ничего не отобразится.
Хм, куда ты звонил UseExceptionHandler
? Потому что порядок регистрации вещей в конвейере имеет большое значение :)
var app = builder.Build(); app.UseExceptionHandler();
Остальные вещи регистрируются позже, например. swagger, httpsredirection, маршрутизация, промежуточное программное обеспечение и контроллеры карт.
всего несколько мнений;
Можете ли вы попробовать опустить тип контента. Просто установите код состояния и передайте объект сведений о проблеме в WriteAsJsonAsync? Вы также можете самостоятельно протестировать сериализацию объекта и передать его с помощью «await context.Response.WriteAsync(serialized json);»
Кстати, какая у вас базовая версия asp.net? IExceptionHandler должен работать с версии 8.0
Вы уверены, что тип контента правильный? «приложение/проблема+json»? По умолчанию должно быть application/json. Проблема может быть связана с типом контента и/или форматированием. Вы можете ознакомиться с соответствующим MS руководством.
Вы уверены, что после этого нет другого промежуточного программного обеспечения, которое могло бы изменить ответ?
Если возможно, я бы проводил отладку с помощью точки останова и шаг за шагом продвигался бы настолько далеко, насколько это возможно, чтобы найти проблему.
Что касается дальнейшего исключения, я понял, что произошел сбой кода в методе WriteAsJsonAsync с исключением «Невозможно получить доступ к закрытому потоку».
Можете ли вы отслеживать стеки, чтобы увидеть, откуда возникает ошибка? Вы можете точно определить, какой поток и откуда он исходит.
Вы уверены, что после этого нет другого промежуточного программного обеспечения, которое могло бы изменить ответ? Как упомянул Михал, порядок регистрации/промежуточного программного обеспечения имеет значение.
Вы уверены, что тип контента правильный? «приложение/проблема+json»? По умолчанию должно быть application/json. Проблема может быть связана с типом контента и/или форматированием. Вы можете ознакомиться с соответствующим [руководством MS][1]. Можете ли вы протестировать отправку текста с типом текстового контента? Клиент может не принять его, вы можете протестировать его с помощью тестового клиента, если это возможно. Вы можете отфильтровать некоторые возможности. Или вы можете просто поставить точку останова на writeasync и идти шаг за шагом, чтобы найти основную причину. Я думаю, таким образом можно отфильтровать множество возможностей.
Это моя трассировка стека: at System.ThrowHelper.ThrowObjectDisposedException_StreamClosed(String objectName) at System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count) at System.IO.MemoryStream.WriteAsync(ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) --- End of stack trace from previous location --- at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.<SerializeAsync>d__9.MoveNext() at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.<SerializeAsync>d__9.MoveNext()
Можете ли вы попробовать опустить тип контента. Просто установите код состояния и передайте объект сведений о проблеме в WriteAsJsonAsync? вы также можете самостоятельно протестировать сериализацию объекта и передать его с помощью «await context.Response.WriteAsync(serialized json);»
Кстати, какая у вас базовая версия asp.net? IExceptionHandler работает с версии 8.0.
Я создаю новый проект своего основного приложения ASP.NET (Модель-Представление-Контроллер), использую .net 8.0, затем копирую ваш класс GlobalExceptionHandler
, затем настраиваю его в файле program.cs. Он работает хорошо.
Программа.cs:
using WebApplication1;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using WebApplication1.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Хоумконтроллер.cs:
public ViewResult Index() => throw new Exception("Index error");
результат:
Моя версия Visual Studio — 17.10.5, а версия .net — .NET 8.0. Вы можете проверить версию .net и Visual Studio, а также порядок регистрации в Program.cs.
Спасибо всем за ответы, они помогли мне устранить возможные причины и найти решение. Проблема была связана с тем, что поток памяти был закрыт до того, как обработчик исключений смог записать в него запись. Мне пришлось заменить блоки using MemoryStream, чтобы они работали без использования, и теперь все работает нормально. Еще раз спасибо!
Предыдущий код:
using (var ms = new MemoryStream())
{
f.CopyTo(ms);
r = ms.ToArray();
}
Изменено на:
var ms = new MemoryStream();
try
{
f.CopyTo(ms);
r = ms.ToArray();
}
finally
{
ms.Dispose();
}
Да, они у меня настроены правильно. но почему-то не работает. Возможно ли, что метод AddProblemDetails() каким-то образом не будет зарегистрирован из-за других дополнений к службам компоновщика?