< Summary

Information
Class: ProjectTemplate.Infrastructure.Data.Extensions.InfrastructureDataAccessServiceExtensions
Assembly: ProjectTemplate.Infrastructure
File(s): /home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Infrastructure/Data/Extensions/InfrastructureDataAccessServiceExtensions.cs
Line coverage
100%
Covered lines: 103
Uncovered lines: 0
Coverable lines: 103
Total lines: 220
Line coverage: 100%
Branch coverage
87%
Covered branches: 28
Total branches: 32
Branch coverage: 87.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddApplicationInfrastructureDataAccess(...)100%44100%
ResolveDataAccessRegistration(...)80%2020100%
ConfigureDataAccess(...)100%44100%
ConfigureProvider(...)100%44100%
Enabled(...)100%11100%
Disabled(...)100%11100%

File(s)

/home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Infrastructure/Data/Extensions/InfrastructureDataAccessServiceExtensions.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using Microsoft.Extensions.Configuration;
 3using Microsoft.Extensions.DependencyInjection;
 4using Microsoft.Extensions.DependencyInjection.Extensions;
 5using Microsoft.Extensions.Logging;
 6using ProjectTemplate.Infrastructure.Data.Auditing;
 7using ProjectTemplate.Infrastructure.Data.ExternalLogins;
 8using ProjectTemplate.Infrastructure.Data.Options;
 9
 10namespace ProjectTemplate.Infrastructure.Data.Extensions;
 11
 12/// <summary>
 13/// Provides infrastructure-owned service registration methods for application data access.
 14/// </summary>
 15public static class InfrastructureDataAccessServiceExtensions
 16{
 17    /// <summary>
 18    /// Registers EF Core data access services for infrastructure and non-web consumers.
 19    /// </summary>
 20    public static IServiceCollection AddApplicationInfrastructureDataAccess(
 21        this IServiceCollection services,
 22        IConfiguration configuration)
 23    {
 14224        ArgumentNullException.ThrowIfNull(services);
 14225        ArgumentNullException.ThrowIfNull(configuration);
 26
 14227        services
 14228            .AddOptions<DataAccessOptions>()
 14229            .Bind(configuration.GetSection(DataAccessOptions.SectionName))
 14230            .Validate(options => !string.IsNullOrWhiteSpace(options.Provider),
 14231                "ProjectTemplate:DataAccess:Provider must not be empty.")
 14232            .Validate(options => DataAccessOptions.IsDisabledProvider(options.Provider)
 14233                || !string.IsNullOrWhiteSpace(options.ConnectionStringName),
 14234                "ProjectTemplate:DataAccess:ConnectionStringName must not be empty when data access is enabled.")
 14235            .Validate(options => options.Diagnostics is not null,
 14236                "ProjectTemplate:DataAccess:Diagnostics must be configured as an object when specified.")
 14237            .Validate(options => AuditStorageModes.IsSupported(options.Auditing.StorageMode),
 14238                "ProjectTemplate:DataAccess:Auditing:StorageMode must be Local, Outbox, or ExternalSink.")
 14239            .ValidateOnStart();
 40
 14241        DataAccessRegistration registration = ResolveDataAccessRegistration(configuration);
 13642        if (registration.IsDisabled)
 43        {
 744            return services;
 45        }
 46
 12947        services.TryAddScoped<ICurrentActorAccessor, SystemCurrentActorAccessor>();
 12948        services.TryAddScoped<IApplicationAuditContextAccessor, SystemApplicationAuditContextAccessor>();
 12949        services.TryAddScoped<IApplicationAuditValuePolicy, DefaultApplicationAuditValuePolicy>();
 12950        services.TryAddSingleton<IApplicationMutationManifestBuilder, CanonicalApplicationMutationManifestBuilder>();
 12951        services.TryAddSingleton<IApplicationMutationManifestHasher, Sha256ApplicationMutationManifestHasher>();
 12952        services.TryAddSingleton<IApplicationMutationManifestVerifier, ApplicationMutationManifestVerifier>();
 53
 12954        if (AuditStorageModes.IsLocal(registration.AuditStorageMode))
 55        {
 12856            services.TryAddScoped<IApplicationAuditStore, LocalApplicationAuditStore>();
 57        }
 58
 12959        services.TryAddScoped<ContextIsolatedApplicationSaveChangesPipeline>();
 12960        services.TryAddScoped<IApplicationSaveChangesPipeline>(serviceProvider =>
 12961            serviceProvider.GetRequiredService<ContextIsolatedApplicationSaveChangesPipeline>());
 12962        services.TryAddScoped<IApplicationMutationAuditReceiptRegistry>(serviceProvider =>
 12963            serviceProvider.GetRequiredService<ContextIsolatedApplicationSaveChangesPipeline>());
 12964        services.TryAddScoped<IApplicationMutationAuditReceiptAccessor, ApplicationDbContextMutationAuditReceiptAccessor
 12965        services.TryAddScoped<ApplicationSaveChangesInterceptor>();
 66
 12967        services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
 12968            ConfigureDataAccess(
 12969                serviceProvider,
 12970                options,
 12971                registration));
 72
 12973        services.AddDbContextFactory<ApplicationDbContext>(
 12974            (serviceProvider, options) => ConfigureDataAccess(
 12975                serviceProvider,
 12976                options,
 12977                registration),
 12978            ServiceLifetime.Scoped);
 79
 12980        services.TryAddScoped<IExternalLoginAccountResolver, EfCoreExternalLoginAccountResolver>();
 12981        return services;
 82    }
 83
 84    private static DataAccessRegistration ResolveDataAccessRegistration(IConfiguration configuration)
 85    {
 14286        DataAccessOptions dataAccessOptions = configuration
 14287            .GetSection(DataAccessOptions.SectionName)
 14288            .Get<DataAccessOptions>() ?? new DataAccessOptions();
 89
 14290        string provider = dataAccessOptions.Provider?.Trim() ?? string.Empty;
 14291        string connectionStringName = dataAccessOptions.ConnectionStringName?.Trim() ?? string.Empty;
 14292        string auditStorageMode = AuditStorageModes.Normalize(dataAccessOptions.Auditing.StorageMode);
 93
 14294        if (string.IsNullOrWhiteSpace(provider))
 95        {
 296            throw new InvalidOperationException("Application data access provider was not configured.");
 97        }
 98
 14099        if (!AuditStorageModes.IsSupported(auditStorageMode))
 100        {
 1101            throw new InvalidOperationException("Application audit storage mode was not configured with a supported valu
 102        }
 103
 139104        if (DataAccessOptions.IsDisabledProvider(provider))
 105        {
 7106            return DataAccessRegistration.Disabled(provider, auditStorageMode);
 107        }
 108
 132109        if (string.IsNullOrWhiteSpace(connectionStringName))
 110        {
 2111            throw new InvalidOperationException("Application data access connection string name was not configured.");
 112        }
 113
 130114        string connectionString = configuration.GetConnectionString(connectionStringName)
 130115            ?? throw new InvalidOperationException($"Connection string '{connectionStringName}' was not configured.");
 116
 129117        return DataAccessRegistration.Enabled(
 129118            provider,
 129119            connectionString,
 129120            auditStorageMode,
 129121            dataAccessOptions.Diagnostics.EnableDetailedErrors,
 129122            dataAccessOptions.Diagnostics.EnableEfCoreTraceBridge);
 123    }
 124
 125    private static void ConfigureDataAccess(
 126        IServiceProvider serviceProvider,
 127        DbContextOptionsBuilder options,
 128        DataAccessRegistration registration)
 129    {
 25130        ConfigureProvider(
 25131            options,
 25132            registration.Provider,
 25133            registration.ConnectionString);
 134
 24135        if (registration.EnableDetailedErrors)
 136        {
 2137            _ = options.EnableDetailedErrors();
 138        }
 139
 24140        if (registration.EnableEfCoreTraceBridge)
 141        {
 2142            ILogger<ApplicationDbContext> logger = serviceProvider
 2143                .GetRequiredService<ILogger<ApplicationDbContext>>();
 144
 2145            _ = options.LogTo(
 2146                message => EfCoreDiagnosticsLogging.LogEfCoreMessage(logger, message),
 2147                LogLevel.Trace);
 148        }
 24149    }
 150
 151    private static void ConfigureProvider(
 152        DbContextOptionsBuilder options,
 153        string provider,
 154        string connectionString)
 155    {
 25156        if (provider.Equals(DataAccessOptions.SqliteProvider, StringComparison.OrdinalIgnoreCase))
 157        {
 18158            options.UseSqlite(connectionString);
 18159            return;
 160        }
 161
 7162        if (provider.Equals(DataAccessOptions.SqlServerProvider, StringComparison.OrdinalIgnoreCase))
 163        {
 6164            options.UseSqlServer(connectionString);
 6165            return;
 166        }
 167
 1168        throw new InvalidOperationException(
 1169            $"Unsupported data access provider '{provider}'. Supported providers: {DataAccessOptions.SqliteProvider}, {D
 170    }
 171
 172    private readonly record struct DataAccessRegistration(
 173        string Provider,
 174        string ConnectionString,
 175        string AuditStorageMode,
 176        bool EnableDetailedErrors,
 177        bool EnableEfCoreTraceBridge,
 178        bool IsDisabled)
 179    {
 180        public static DataAccessRegistration Enabled(
 181            string provider,
 182            string connectionString,
 183            string auditStorageMode,
 184            bool enableDetailedErrors,
 185            bool enableEfCoreTraceBridge)
 186        {
 129187            return new(
 129188                provider,
 129189                connectionString,
 129190                auditStorageMode,
 129191                enableDetailedErrors,
 129192                enableEfCoreTraceBridge,
 129193                false);
 194        }
 195
 196        public static DataAccessRegistration Disabled(
 197            string provider,
 198            string auditStorageMode)
 199        {
 7200            return new(
 7201                provider,
 7202                string.Empty,
 7203                auditStorageMode,
 7204                false,
 7205                false,
 7206                true);
 207        }
 208    }
 209}
 210
 211internal static partial class EfCoreDiagnosticsLogging
 212{
 213    [LoggerMessage(
 214        EventId = 19000,
 215        Level = LogLevel.Trace,
 216        Message = "{EfCoreMessage}")]
 217    internal static partial void LogEfCoreMessage(
 218        ILogger logger,
 219        string efCoreMessage);
 220}