< Summary

Information
Class: ProjectTemplate.Web.ErrorHandling.ProblemDetailsExtensions
Assembly: ProjectTemplate.Web
File(s): /home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Web/ErrorHandling/ProblemDetailsExtensions.cs
Line coverage
98%
Covered lines: 69
Uncovered lines: 1
Coverable lines: 70
Total lines: 148
Line coverage: 98.5%
Branch coverage
75%
Covered branches: 9
Total branches: 12
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddApplicationProblemDetails(...)100%11100%
UseProblemDetails(...)50%2291.66%
GetCorrelationId(...)80%1010100%

File(s)

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

#LineLine coverage
 1using System.Diagnostics;
 2using Microsoft.AspNetCore.Diagnostics;
 3using Microsoft.Extensions.Options;
 4using Microsoft.Extensions.Primitives;
 5using ProjectTemplate.Web.Extensions;
 6using ProjectTemplate.Web.Options;
 7
 8namespace ProjectTemplate.Web.ErrorHandling;
 9
 10/// <summary>
 11/// Provides extension methods for configuring standardized problem details and exception handling in ASP.NET Core
 12/// applications.
 13/// </summary>
 14/// <remarks>These extensions integrate custom problem details formatting and exception handling into the
 15/// application's dependency injection and middleware pipelines. They help ensure consistent error responses and
 16/// traceability across environments.</remarks>
 17internal static class ProblemDetailsExtensions
 18{
 19    /// <summary>
 20    /// Adds standardized Problem Details error responses and a custom exception handler to the application's service
 21    /// collection.
 22    /// </summary>
 23    /// <remarks>This method configures Problem Details middleware to include trace and request identifiers in
 24    /// error responses and customizes error details for non-development environments. It also registers a custom
 25    /// exception handler for consistent error formatting.</remarks>
 26    /// <param name="services">The service collection to which Problem Details and the exception handler are added. Cann
 27    /// <param name="webHostEnvironment">The current web hosting environment. Used to determine error detail behavior ba
 28    /// null.</param>
 29    /// <returns>The same service collection instance, enabling method chaining.</returns>
 30    public static IServiceCollection AddApplicationProblemDetails(
 31        this IServiceCollection services,
 32        IWebHostEnvironment webHostEnvironment)
 33    {
 11434        ArgumentNullException.ThrowIfNull(services);
 11435        ArgumentNullException.ThrowIfNull(webHostEnvironment);
 36
 11437        services.AddExceptionHandler<ProblemDetailsExceptionHandler>();
 38
 11439        services.AddProblemDetails(options => options.CustomizeProblemDetails = context =>
 11440            {
 11441                string? originalExceptionPath = context.HttpContext.Features
 11442                    .Get<IExceptionHandlerPathFeature>()?
 11443                    .Path;
 11444
 11445                string? originalStatusCodePath = context.HttpContext.Features
 11446                    .Get<IStatusCodeReExecuteFeature>()?
 11447                    .OriginalPath;
 11448
 11449                context.ProblemDetails.Instance ??= !string.IsNullOrWhiteSpace(originalExceptionPath)
 11450                    ? originalExceptionPath
 11451                    : !string.IsNullOrWhiteSpace(originalStatusCodePath)
 11452                        ? originalStatusCodePath
 11453                        : context.HttpContext.Request.Path;
 11454
 11455                string correlationId = GetCorrelationId(context.HttpContext);
 11456
 11457                Activity? activity = Activity.Current;
 11458
 11459                context.ProblemDetails.Extensions["traceId"] =
 11460                    activity?.TraceId.ToString() ?? context.HttpContext.TraceIdentifier;
 11461
 11462                if (activity is not null)
 11463                {
 11464                    context.ProblemDetails.Extensions["spanId"] = activity.SpanId.ToString();
 11465                }
 11466
 11467                context.ProblemDetails.Extensions["requestId"] =
 11468                    context.HttpContext.TraceIdentifier;
 11469
 11470                context.ProblemDetails.Extensions["correlationId"] =
 11471                    correlationId;
 11472
 11473                if (!webHostEnvironment.IsDevelopment() &&
 11474                    context.ProblemDetails.Status >= StatusCodes.Status500InternalServerError)
 11475                {
 11476                    context.ProblemDetails.Detail ??=
 11477                        "An unexpected error occurred. Contact support with the request ID.";
 11478                }
 11479            });
 80
 11481        return services;
 82    }
 83
 84    /// <summary>
 85    /// Configures standardized error handling and problem details middleware for the application based on the current
 86    /// environment.
 87    /// </summary>
 88    /// <remarks>In the development environment, this method enables the developer exception page. In other
 89    /// environments, it configures a generic exception handler, followed by HSTS through
 90    /// <c>SecurityHeadersExtensions.UseApplicationHsts</c> (defined with the security headers, registered here to keep
 91    /// it ahead of the status-code branches). It also sets up status code pages to return problem details responses whe
 92    /// error page otherwise.</remarks>
 93    /// <param name="app">The <see cref="WebApplication"/> instance to configure. Cannot be null.</param>
 94    /// <returns>The configured <see cref="WebApplication"/> instance.</returns>
 95    public static WebApplication UseProblemDetails(this WebApplication app)
 96    {
 10797        ArgumentNullException.ThrowIfNull(app);
 98
 10799        if (app.Environment.IsDevelopment())
 100        {
 0101            app.UseDeveloperExceptionPage();
 102        }
 103        else
 104        {
 107105            app.UseExceptionHandler("/Home/Error/500");
 106        }
 107
 108        // HSTS belongs between the exception handler and the status-code branches below, so it runs once per
 109        // request and is not re-invoked by status-code re-execution. The implementation lives with the other
 110        // security headers; only the registration point is here. It is a no-op in development.
 107111        app.UseApplicationHsts();
 112
 107113        app.UseWhen(
 107114            ProblemDetailsRequestClassifier.ShouldWriteProblemDetails,
 107115            branch => branch.UseStatusCodePages());
 116
 107117        app.UseWhen(
 107118            context => !ProblemDetailsRequestClassifier.ShouldWriteProblemDetails(context),
 107119            branch => branch.UseStatusCodePagesWithReExecute("/Home/Error/{0}"));
 120
 107121        return app;
 122    }
 123
 124    private static string GetCorrelationId(HttpContext httpContext)
 125    {
 10126        ApplicationRequestLoggingOptions requestLoggingOptions = httpContext.RequestServices
 10127            .GetService<IOptions<ApplicationRequestLoggingOptions>>()?.Value
 10128            ?? new ApplicationRequestLoggingOptions();
 129
 10130        if (httpContext.Request.Headers.TryGetValue(
 10131            requestLoggingOptions.CorrelationHeaderName,
 10132            out StringValues correlationHeaderValues))
 133        {
 2134            string? headerValue = correlationHeaderValues.FirstOrDefault();
 135
 2136            if (!string.IsNullOrWhiteSpace(headerValue))
 137            {
 2138                string cleanValue = headerValue.Trim();
 139
 2140                return cleanValue.Length <= 128
 2141                    ? cleanValue
 2142                    : cleanValue[..128];
 143            }
 144        }
 145
 8146        return httpContext.TraceIdentifier;
 147    }
 148}