< Summary

Information
Class: ProjectTemplate.Web.ErrorHandling.ProblemDetailsExceptionHandler
Assembly: ProjectTemplate.Web
File(s): /home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Web/ErrorHandling/ProblemDetailsExceptionHandler.cs
Line coverage
98%
Covered lines: 66
Uncovered lines: 1
Coverable lines: 67
Total lines: 201
Line coverage: 98.5%
Branch coverage
89%
Covered branches: 25
Total branches: 28
Branch coverage: 89.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
TryHandleAsync()100%44100%
CreateProblemDetails(...)75%88100%
GetStatusCode(...)100%66100%
GetTitle(...)87.5%8887.5%
LogException(...)100%22100%

File(s)

/home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Web/ErrorHandling/ProblemDetailsExceptionHandler.cs

#LineLine coverage
 1using System.Diagnostics;
 2using Microsoft.AspNetCore.Diagnostics;
 3using Microsoft.AspNetCore.Mvc;
 4
 5namespace ProjectTemplate.Web.ErrorHandling;
 6
 7/// <summary>
 8/// Handles exceptions by generating and writing RFC 7807 Problem Details responses for HTTP requests when appropriate.
 9/// </summary>
 10/// <remarks>This exception handler inspects the HTTP context and exception to determine whether a Problem Details
 11/// response should be written. It sets the response status code and includes trace information for diagnostics. In
 12/// development environments, detailed exception messages are included in the response; in production, a generic error
 13/// message is provided for server errors. The handler does not write a response if the request does not expect Problem
 14/// Details or if the response has already started.</remarks>
 15/// <param name="problemDetailsService">The service used to write Problem Details responses to the HTTP response.</param
 16/// <param name="webHostEnvironment">The hosting environment used to determine whether to include detailed error informa
 17/// <param name="logger">The logger used to record exception and error information.</param>
 15818internal sealed class ProblemDetailsExceptionHandler(
 15819    IProblemDetailsService problemDetailsService,
 15820    IWebHostEnvironment webHostEnvironment,
 15821    ILogger<ProblemDetailsExceptionHandler> logger) : IExceptionHandler
 22{
 23    /// <summary>
 24    /// Attempts to handle the specified exception by generating and writing a Problem Details response to the HTTP
 25    /// context asynchronously.
 26    /// </summary>
 27    /// <remarks>A Problem Details response is only written if the request is classified as requiring Problem
 28    /// Details and the response has not already started. If the response cannot be written, the method returns <see
 29    /// langword="false"/> and does not modify the response.</remarks>
 30    /// <param name="httpContext">The HTTP context for the current request. Cannot be null.</param>
 31    /// <param name="exception">The exception to handle and convert into a Problem Details response. Cannot be null.</pa
 32    /// <param name="cancellationToken">A token that can be used to cancel the asynchronous operation.</param>
 33    /// <returns>A task that represents the asynchronous operation. The task result contains <see langword="true"/> if a
 34    /// Details response was written; otherwise, <see langword="false"/>.</returns>
 35    public async ValueTask<bool> TryHandleAsync(
 36        HttpContext httpContext,
 37        Exception exception,
 38        CancellationToken cancellationToken)
 39    {
 2240        ArgumentNullException.ThrowIfNull(httpContext);
 2241        ArgumentNullException.ThrowIfNull(exception);
 42
 2243        if (!ProblemDetailsRequestClassifier.ShouldWriteProblemDetails(httpContext))
 44        {
 245            return false;
 46        }
 47
 2048        if (httpContext.Response.HasStarted)
 49        {
 250            return false;
 51        }
 52
 1853        ProblemDetails problemDetails = CreateProblemDetails(httpContext, exception);
 54
 1855        LogException(logger, exception, problemDetails.Status ?? StatusCodes.Status500InternalServerError, httpContext);
 56
 1857        httpContext.Response.StatusCode = problemDetails.Status ?? StatusCodes.Status500InternalServerError;
 58
 1859        var problemDetailsContext = new ProblemDetailsContext
 1860        {
 1861            HttpContext = httpContext,
 1862            ProblemDetails = problemDetails
 1863        };
 64
 1865        return await problemDetailsService.TryWriteAsync(problemDetailsContext);
 2266    }
 67
 68    private ProblemDetails CreateProblemDetails(HttpContext httpContext, Exception exception)
 69    {
 1870        int statusCode = GetStatusCode(exception);
 1871        string title = GetTitle(statusCode);
 72
 1873        var problemDetails = new ProblemDetails
 1874        {
 1875            Status = statusCode,
 1876            Title = title,
 1877            Type = $"https://httpstatuses.com/{statusCode}",
 1878            Instance = httpContext.Request.Path
 1879        };
 80
 1881        problemDetails.Extensions["traceId"] = Activity.Current?.Id ?? httpContext.TraceIdentifier;
 1882        problemDetails.Extensions["requestId"] = httpContext.TraceIdentifier;
 83
 1884        if (webHostEnvironment.IsDevelopment())
 85        {
 286            problemDetails.Detail = exception.Message;
 87        }
 1688        else if (statusCode >= StatusCodes.Status500InternalServerError)
 89        {
 1090            problemDetails.Detail = "An unexpected error occurred. Contact support with the request ID.";
 91        }
 92
 1893        return problemDetails;
 94    }
 95
 96    private static int GetStatusCode(Exception exception)
 97    {
 1898        return exception switch
 1899        {
 4100            BadHttpRequestException => StatusCodes.Status400BadRequest,
 2101            UnauthorizedAccessException => StatusCodes.Status403Forbidden,
 2102            TimeoutException => StatusCodes.Status503ServiceUnavailable,
 18103            // Plain ArgumentException is intentionally treated as an internal failure. Broad argument failures can
 18104            // represent server-side developer bugs; request-level failures should use explicit client-input types.
 10105            _ => StatusCodes.Status500InternalServerError
 18106        };
 107    }
 108
 109    private static string GetTitle(int statusCode)
 110    {
 18111        return statusCode switch
 18112        {
 4113            StatusCodes.Status400BadRequest => "Bad Request",
 2114            StatusCodes.Status403Forbidden => "Forbidden",
 0115            StatusCodes.Status404NotFound => "Not Found",
 2116            StatusCodes.Status503ServiceUnavailable => "Service Unavailable",
 10117            _ => "Internal Server Error"
 18118        };
 119    }
 120
 121    private static void LogException(
 122        ILogger logger,
 123        Exception exception,
 124        int statusCode,
 125        HttpContext httpContext)
 126    {
 18127        if (statusCode >= StatusCodes.Status500InternalServerError)
 128        {
 12129            ProblemDetailsLogMessages.UnhandledException(
 12130                logger,
 12131                exception,
 12132                statusCode,
 12133                httpContext.TraceIdentifier,
 12134                httpContext.Request.Path);
 135        }
 136        else
 137        {
 6138            ProblemDetailsLogMessages.HandledException(
 6139                logger,
 6140                exception,
 6141                statusCode,
 6142                httpContext.TraceIdentifier,
 6143                httpContext.Request.Path);
 144        }
 6145    }
 146}
 147
 148/// <summary>
 149/// Provides strongly-typed logging methods for recording events related to the conversion of exceptions to Problem
 150/// Details responses.
 151/// </summary>
 152/// <remarks>This class defines logging message templates for use with the Microsoft.Extensions.Logging source
 153/// generator. The methods are intended for internal use to standardize log output when exceptions are converted to
 154/// Problem Details in HTTP responses.</remarks>
 155internal static partial class ProblemDetailsLogMessages
 156{
 157    /// <summary>
 158    /// Logs an unhandled exception as an error, including HTTP status code, request identifier, and request path
 159    /// information.
 160    /// </summary>
 161    /// <remarks>This method is intended for use in centralized exception handling scenarios to ensure
 162    /// consistent logging of unhandled exceptions with relevant request context.</remarks>
 163    /// <param name="logger">The logger used to write the error message.</param>
 164    /// <param name="exception">The exception that was not handled.</param>
 165    /// <param name="statusCode">The HTTP status code associated with the error response.</param>
 166    /// <param name="requestId">The unique identifier for the current request.</param>
 167    /// <param name="path">The request path where the exception occurred.</param>
 168    [LoggerMessage(
 169        EventId = 32001,
 170        Level = LogLevel.Error,
 171        Message = "Unhandled exception converted to Problem Details. StatusCode: {StatusCode}. RequestId: {RequestId}. P
 172    public static partial void UnhandledException(
 173        ILogger logger,
 174        Exception exception,
 175        int statusCode,
 176        string requestId,
 177        string path);
 178
 179    /// <summary>
 180    /// Logs a warning message indicating that a handled exception was converted to a Problem Details response,
 181    /// including status code, request ID, and request path information.
 182    /// </summary>
 183    /// <remarks>This method is intended for use in exception handling middleware or filters to provide
 184    /// consistent logging of handled exceptions that result in Problem Details responses. The log entry includes
 185    /// contextual information to aid in troubleshooting.</remarks>
 186    /// <param name="logger">The logger used to write the warning message.</param>
 187    /// <param name="exception">The exception that was handled and converted to a Problem Details response.</param>
 188    /// <param name="statusCode">The HTTP status code associated with the Problem Details response.</param>
 189    /// <param name="requestId">The unique identifier for the request in which the exception occurred.</param>
 190    /// <param name="path">The request path where the exception was handled.</param>
 191    [LoggerMessage(
 192        EventId = 32002,
 193        Level = LogLevel.Warning,
 194        Message = "Handled exception converted to Problem Details. StatusCode: {StatusCode}. RequestId: {RequestId}. Pat
 195    public static partial void HandledException(
 196        ILogger logger,
 197        Exception exception,
 198        int statusCode,
 199        string requestId,
 200        string path);
 201}