< 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: 65
Uncovered lines: 1
Coverable lines: 66
Total lines: 200
Line coverage: 98.4%
Branch coverage
90%
Covered branches: 27
Total branches: 30
Branch coverage: 90%
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%88100%
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>
 14618internal sealed class ProblemDetailsExceptionHandler(
 14619    IProblemDetailsService problemDetailsService,
 14620    IWebHostEnvironment webHostEnvironment,
 14621    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    {
 2040        ArgumentNullException.ThrowIfNull(httpContext);
 2041        ArgumentNullException.ThrowIfNull(exception);
 42
 2043        if (!ProblemDetailsRequestClassifier.ShouldWriteProblemDetails(httpContext))
 44        {
 245            return false;
 46        }
 47
 1848        if (httpContext.Response.HasStarted)
 49        {
 250            return false;
 51        }
 52
 1653        ProblemDetails problemDetails = CreateProblemDetails(httpContext, exception);
 54
 1655        LogException(logger, exception, problemDetails.Status ?? StatusCodes.Status500InternalServerError, httpContext);
 56
 1657        httpContext.Response.StatusCode = problemDetails.Status ?? StatusCodes.Status500InternalServerError;
 58
 1659        var problemDetailsContext = new ProblemDetailsContext
 1660        {
 1661            HttpContext = httpContext,
 1662            ProblemDetails = problemDetails
 1663        };
 64
 1665        return await problemDetailsService.TryWriteAsync(problemDetailsContext);
 2066    }
 67
 68    private ProblemDetails CreateProblemDetails(HttpContext httpContext, Exception exception)
 69    {
 1670        int statusCode = GetStatusCode(exception);
 1671        string title = GetTitle(statusCode);
 72
 1673        var problemDetails = new ProblemDetails
 1674        {
 1675            Status = statusCode,
 1676            Title = title,
 1677            Type = $"https://httpstatuses.com/{statusCode}",
 1678            Instance = httpContext.Request.Path
 1679        };
 80
 1681        problemDetails.Extensions["traceId"] = Activity.Current?.Id ?? httpContext.TraceIdentifier;
 1682        problemDetails.Extensions["requestId"] = httpContext.TraceIdentifier;
 83
 1684        if (webHostEnvironment.IsDevelopment())
 85        {
 286            problemDetails.Detail = exception.Message;
 87        }
 1488        else if (statusCode >= StatusCodes.Status500InternalServerError)
 89        {
 690            problemDetails.Detail = "An unexpected error occurred. Contact support with the request ID.";
 91        }
 92
 1693        return problemDetails;
 94    }
 95
 96    private static int GetStatusCode(Exception exception)
 97    {
 1698        return exception switch
 1699        {
 2100            BadHttpRequestException => StatusCodes.Status400BadRequest,
 6101            ArgumentException => StatusCodes.Status400BadRequest,
 2102            UnauthorizedAccessException => StatusCodes.Status403Forbidden,
 2103            TimeoutException => StatusCodes.Status503ServiceUnavailable,
 4104            _ => StatusCodes.Status500InternalServerError
 16105        };
 106    }
 107
 108    private static string GetTitle(int statusCode)
 109    {
 16110        return statusCode switch
 16111        {
 8112            StatusCodes.Status400BadRequest => "Bad Request",
 2113            StatusCodes.Status403Forbidden => "Forbidden",
 0114            StatusCodes.Status404NotFound => "Not Found",
 2115            StatusCodes.Status503ServiceUnavailable => "Service Unavailable",
 4116            _ => "Internal Server Error"
 16117        };
 118    }
 119
 120    private static void LogException(
 121        ILogger logger,
 122        Exception exception,
 123        int statusCode,
 124        HttpContext httpContext)
 125    {
 16126        if (statusCode >= StatusCodes.Status500InternalServerError)
 127        {
 6128            ProblemDetailsLogMessages.UnhandledException(
 6129                logger,
 6130                exception,
 6131                statusCode,
 6132                httpContext.TraceIdentifier,
 6133                httpContext.Request.Path);
 134        }
 135        else
 136        {
 10137            ProblemDetailsLogMessages.HandledException(
 10138                logger,
 10139                exception,
 10140                statusCode,
 10141                httpContext.TraceIdentifier,
 10142                httpContext.Request.Path);
 143        }
 10144    }
 145}
 146
 147/// <summary>
 148/// Provides strongly-typed logging methods for recording events related to the conversion of exceptions to Problem
 149/// Details responses.
 150/// </summary>
 151/// <remarks>This class defines logging message templates for use with the Microsoft.Extensions.Logging source
 152/// generator. The methods are intended for internal use to standardize log output when exceptions are converted to
 153/// Problem Details in HTTP responses.</remarks>
 154internal static partial class ProblemDetailsLogMessages
 155{
 156    /// <summary>
 157    /// Logs an unhandled exception as an error, including HTTP status code, request identifier, and request path
 158    /// information.
 159    /// </summary>
 160    /// <remarks>This method is intended for use in centralized exception handling scenarios to ensure
 161    /// consistent logging of unhandled exceptions with relevant request context.</remarks>
 162    /// <param name="logger">The logger used to write the error message.</param>
 163    /// <param name="exception">The exception that was not handled.</param>
 164    /// <param name="statusCode">The HTTP status code associated with the error response.</param>
 165    /// <param name="requestId">The unique identifier for the current request.</param>
 166    /// <param name="path">The request path where the exception occurred.</param>
 167    [LoggerMessage(
 168        EventId = 32001,
 169        Level = LogLevel.Error,
 170        Message = "Unhandled exception converted to Problem Details. StatusCode: {StatusCode}. RequestId: {RequestId}. P
 171    public static partial void UnhandledException(
 172        ILogger logger,
 173        Exception exception,
 174        int statusCode,
 175        string requestId,
 176        string path);
 177
 178    /// <summary>
 179    /// Logs a warning message indicating that a handled exception was converted to a Problem Details response,
 180    /// including status code, request ID, and request path information.
 181    /// </summary>
 182    /// <remarks>This method is intended for use in exception handling middleware or filters to provide
 183    /// consistent logging of handled exceptions that result in Problem Details responses. The log entry includes
 184    /// contextual information to aid in troubleshooting.</remarks>
 185    /// <param name="logger">The logger used to write the warning message.</param>
 186    /// <param name="exception">The exception that was handled and converted to a Problem Details response.</param>
 187    /// <param name="statusCode">The HTTP status code associated with the Problem Details response.</param>
 188    /// <param name="requestId">The unique identifier for the request in which the exception occurred.</param>
 189    /// <param name="path">The request path where the exception was handled.</param>
 190    [LoggerMessage(
 191        EventId = 32002,
 192        Level = LogLevel.Warning,
 193        Message = "Handled exception converted to Problem Details. StatusCode: {StatusCode}. RequestId: {RequestId}. Pat
 194    public static partial void HandledException(
 195        ILogger logger,
 196        Exception exception,
 197        int statusCode,
 198        string requestId,
 199        string path);
 200}