Skip to content

Commit 945c3c3

Browse files
committed
Restore custom status code response functionality for exceptions thrown outside of HttpActionInvoker
1 parent 52791a6 commit 945c3c3

3 files changed

Lines changed: 49 additions & 16 deletions

File tree

src/Dibix.Http.Host/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ void ConfigureLogging(ILoggingBuilder logging)
6868
.AddTransient<IPostConfigureOptions<JsonOptions>, JsonPostConfigureOptions>()
6969
.AddSingleton<IControllerActivator, NotSupportedControllerActivator>();
7070

71+
services.AddExceptionHandler<DatabaseAccessExceptionHandler>();
7172
services.AddExceptionHandler<HttpRequestExecutionExceptionHandler>();
7273
services.AddProblemDetailsWithMapping()
7374
.Map<HttpRequestExecutionException>(x => x.IsClientError, (x, y) =>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Dibix.Http.Server;
5+
using Microsoft.AspNetCore.Diagnostics;
6+
using Microsoft.AspNetCore.Http;
7+
8+
namespace Dibix.Http.Host
9+
{
10+
// Map sql error codes to http status codes globally
11+
// This is needed if a DatabaseAccessException is thrown outside HttpActionInvoker. For example, within the http host extension.
12+
internal sealed class DatabaseAccessExceptionHandler : HttpRequestExecutionExceptionHandler, IExceptionHandler
13+
{
14+
public DatabaseAccessExceptionHandler(IProblemDetailsService problemDetailsService) : base(problemDetailsService) { }
15+
16+
public override async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
17+
{
18+
if (exception is not DatabaseAccessException databaseAccessException)
19+
return false;
20+
21+
if (SqlHttpStatusCodeParser.TryParse(databaseAccessException, out HttpRequestExecutionException httpRequestExecutionException))
22+
return await base.TryHandleAsync(httpContext, httpRequestExecutionException, cancellationToken).ConfigureAwait(false);
23+
24+
return false;
25+
}
26+
}
27+
}

src/Dibix.Http.Host/Runtime/HttpRequestExecutionExceptionHandler.cs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
namespace Dibix.Http.Host
1010
{
1111
// Map custom HTTP status codes to response
12-
internal sealed class HttpRequestExecutionExceptionHandler : IExceptionHandler
12+
internal class HttpRequestExecutionExceptionHandler : IExceptionHandler
1313
{
1414
private readonly IProblemDetailsService _problemDetailsService;
1515

@@ -18,8 +18,25 @@ public HttpRequestExecutionExceptionHandler(IProblemDetailsService problemDetail
1818
_problemDetailsService = problemDetailsService;
1919
}
2020

21-
public ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) => TryHandleWithProblemDetails(httpContext, exception);
22-
21+
public virtual ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) => TryHandleWithProblemDetails(httpContext, exception);
22+
23+
protected static bool HandleHttpRequestExecutionException(HttpContext httpContext, HttpRequestExecutionException httpRequestExecutionException)
24+
{
25+
httpContext.Response.StatusCode = (int)httpRequestExecutionException.StatusCode;
26+
27+
// For compatibility reasons
28+
// TODO: Remove, once problem details are stabilized
29+
httpRequestExecutionException.AppendToResponse(httpContext.Response);
30+
31+
// Note: The ExceptionHandlerMiddleware will log the exception even if the handler returns true
32+
// See: https://github.com/dotnet/aspnetcore/issues/54554
33+
// If client exceptions should not be logged, we can do the following:
34+
// - Implement a custom IExceptionHandler
35+
// - Use LogError with a custom log category (i.E. the name of the handler, something like 'HttpStatusCodeClientExceptionHandler')
36+
// - Set this category's log level to 'Critical' by default, to avoid logging client exceptions
37+
return true;
38+
}
39+
2340
// TODO: Instead of writing problem details manually, use the StatusCodeSelector introduced in .NET 9
2441
// See: https://www.milanjovanovic.tech/blog/problem-details-for-aspnetcore-apis#handling-specific-exceptions-status-codes
2542
private async ValueTask<bool> TryHandleWithProblemDetails(HttpContext httpContext, Exception exception)
@@ -41,19 +58,7 @@ private static bool TryHandle(HttpContext httpContext, Exception exception)
4158
if (exception is not HttpRequestExecutionException httpRequestExecutionException)
4259
return false;
4360

44-
httpContext.Response.StatusCode = (int)httpRequestExecutionException.StatusCode;
45-
46-
// For compatibility reasons
47-
// TODO: Remove, once problem details are stabilized
48-
httpRequestExecutionException.AppendToResponse(httpContext.Response);
49-
50-
// Note: The ExceptionHandlerMiddleware will log the exception even if the handler returns true
51-
// See: https://github.com/dotnet/aspnetcore/issues/54554
52-
// If client exceptions should not be logged, we can do the following:
53-
// - Implement a custom IExceptionHandler
54-
// - Use LogError with a custom log category (i.E. the name of the handler, something like 'HttpStatusCodeClientExceptionHandler')
55-
// - Set this category's log level to 'Critical' by default, to avoid logging client exceptions
56-
return true;
61+
return HandleHttpRequestExecutionException(httpContext, httpRequestExecutionException);
5762
}
5863
}
5964
}

0 commit comments

Comments
 (0)