< Summary

Information
Class: AsiBackbone.AspNetCore.Correlation.HttpContextAsiBackboneRequestCorrelationResolver
Assembly: AsiBackbone.AspNetCore
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Correlation/HttpContextAsiBackboneRequestCorrelationResolver.cs
Line coverage
100%
Covered lines: 52
Uncovered lines: 0
Coverable lines: 52
Total lines: 152
Line coverage: 100%
Branch coverage
86%
Covered branches: 57
Total branches: 66
Branch coverage: 86.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%66100%
ResolveRequestCorrelation()75%44100%
ResolveCorrelationId(...)100%1212100%
NormalizeClientCorrelationId(...)100%88100%
ResolveTraceId(...)50%44100%
ResolveMetadata(...)90.62%3232100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Correlation/HttpContextAsiBackboneRequestCorrelationResolver.cs

#LineLine coverage
 1using System.Diagnostics;
 2using AsiBackbone.AspNetCore.DependencyInjection;
 3using AsiBackbone.Core;
 4using Microsoft.AspNetCore.Http;
 5using Microsoft.AspNetCore.Routing;
 6using Microsoft.Extensions.Options;
 7using Microsoft.Extensions.Primitives;
 8
 9namespace AsiBackbone.AspNetCore.Correlation;
 10
 11/// <summary>
 12/// Resolves safe request correlation data from the current ASP.NET Core HTTP context.
 13/// </summary>
 14/// <remarks>
 15/// Client-supplied correlation identifiers are accepted only when their trimmed value is printable and does not exceed
 16/// <see cref="AsiBackboneIdentifierLimits.MaximumLength" /> characters. Invalid header values are ignored so the resolv
 17/// can continue to another configured value or use the existing host-generated fallback.
 18/// </remarks>
 19public sealed class HttpContextAsiBackboneRequestCorrelationResolver : IAsiBackboneHttpRequestCorrelationResolver
 20{
 21    private readonly IHttpContextAccessor httpContextAccessor;
 22    private readonly AsiBackboneAspNetCoreOptions options;
 23
 24    /// <summary>
 25    /// Initializes a new instance of the <see cref="HttpContextAsiBackboneRequestCorrelationResolver" /> class.
 26    /// </summary>
 27    /// <param name="httpContextAccessor">The ASP.NET Core HTTP context accessor.</param>
 28    /// <param name="options">The request correlation options.</param>
 4829    public HttpContextAsiBackboneRequestCorrelationResolver(
 4830        IHttpContextAccessor httpContextAccessor,
 4831        IOptions<AsiBackboneAspNetCoreOptions> options)
 32    {
 4833        this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
 4834        this.options = options?.Value ?? throw new ArgumentNullException(nameof(options));
 4835        this.options.Validate();
 4836    }
 37
 38    /// <inheritdoc />
 39    public AsiBackboneHttpRequestCorrelation ResolveRequestCorrelation()
 40    {
 4341        HttpContext? httpContext = httpContextAccessor.HttpContext;
 42
 4343        return httpContext is null
 4344            ? new AsiBackboneHttpRequestCorrelation(traceId: Activity.Current?.Id)
 4345            : new AsiBackboneHttpRequestCorrelation(
 4346            ResolveCorrelationId(httpContext),
 4347            ResolveTraceId(httpContext),
 4348            ResolveMetadata(httpContext));
 49    }
 50
 51    private string? ResolveCorrelationId(HttpContext httpContext)
 52    {
 31553        foreach (string headerName in options.CorrelationIdHeaderNames)
 54        {
 11855            if (string.IsNullOrWhiteSpace(headerName))
 56            {
 57                continue;
 58            }
 59
 11760            if (!httpContext.Request.Headers.TryGetValue(headerName, out StringValues values))
 61            {
 62                continue;
 63            }
 64
 5365            foreach (string? candidate in values)
 66            {
 1567                string? normalizedCandidate = NormalizeClientCorrelationId(candidate);
 1568                if (normalizedCandidate is not null)
 69                {
 570                    return normalizedCandidate;
 71                }
 72            }
 73        }
 74
 3775        return options.UseHttpContextTraceIdentifierAsCorrelationId
 3776            ? httpContext.TraceIdentifier
 3777            : null;
 578    }
 79
 80    private static string? NormalizeClientCorrelationId(string? value)
 81    {
 1582        if (string.IsNullOrWhiteSpace(value))
 83        {
 284            return null;
 85        }
 86
 1387        string normalizedValue = value.Trim();
 1388        if (normalizedValue.Length > AsiBackboneIdentifierLimits.MaximumLength)
 89        {
 190            return null;
 91        }
 92
 56793        foreach (char character in normalizedValue)
 94        {
 27595            if (char.IsControl(character))
 96            {
 797                return null;
 98            }
 99        }
 100
 5101        return normalizedValue;
 102    }
 103
 104    private static string? ResolveTraceId(HttpContext httpContext)
 105    {
 42106        return Activity.Current?.Id ?? httpContext.TraceIdentifier;
 107    }
 108
 109    private Dictionary<string, string> ResolveMetadata(HttpContext httpContext)
 110    {
 42111        Dictionary<string, string> metadata = new(StringComparer.Ordinal);
 112
 42113        if (options.IncludeRequestMethod && !string.IsNullOrWhiteSpace(httpContext.Request.Method))
 114        {
 2115            metadata[AsiBackboneHttpRequestMetadataKeys.Method] = httpContext.Request.Method.Trim();
 116        }
 117
 42118        if (options.IncludeRequestPath && httpContext.Request.Path.HasValue)
 119        {
 1120            metadata[AsiBackboneHttpRequestMetadataKeys.Path] = httpContext.Request.Path.Value;
 121        }
 122
 42123        Endpoint? endpoint = httpContext.GetEndpoint();
 42124        var routeEndpoint = endpoint as RouteEndpoint;
 125
 42126        if (options.IncludeEndpointDisplayName && !string.IsNullOrWhiteSpace(endpoint?.DisplayName))
 127        {
 4128            metadata[AsiBackboneHttpRequestMetadataKeys.EndpointDisplayName] = endpoint.DisplayName.Trim();
 129        }
 130
 42131        if (options.IncludeRoutePattern && !string.IsNullOrWhiteSpace(routeEndpoint?.RoutePattern.RawText))
 132        {
 2133            metadata[AsiBackboneHttpRequestMetadataKeys.RoutePattern] = routeEndpoint.RoutePattern.RawText.Trim();
 134        }
 135
 42136        if (options.IncludeRouteValues)
 137        {
 90138            foreach (KeyValuePair<string, object?> routeValue in httpContext.Request.RouteValues)
 139            {
 4140                if (routeValue.Value is null || string.IsNullOrWhiteSpace(routeValue.Key))
 141                {
 142                    continue;
 143                }
 144
 2145                metadata[$"{AsiBackboneHttpRequestMetadataKeys.RouteValuePrefix}{routeValue.Key.Trim()}"] =
 2146                    routeValue.Value.ToString()?.Trim() ?? string.Empty;
 147            }
 148        }
 149
 42150        return metadata;
 151    }
 152}