using Microsoft.AspNetCore.DataProtection; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using ScadaLink.Commons.Interfaces.Repositories; using ScadaLink.Commons.Interfaces.Services; using ScadaLink.ConfigurationDatabase.Repositories; using ScadaLink.ConfigurationDatabase.Services; namespace ScadaLink.ConfigurationDatabase; public static class ServiceCollectionExtensions { /// /// Registers the ScadaLinkDbContext with the provided SQL Server connection string. /// public static IServiceCollection AddConfigurationDatabase(this IServiceCollection services, string connectionString) { // The DbContext is constructed via the (options, IDataProtectionProvider) overload so // secret-bearing configuration columns are encrypted at rest. AddDataProtection below // registers IDataProtectionProvider as a singleton; resolving it here does not recurse // because key-ring loading is lazy (first Protect/Unprotect), not triggered by // CreateProtector during model building. services.AddDbContext((serviceProvider, options) => { options.UseSqlServer(connectionString) .ConfigureWarnings(w => w.Ignore( Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)); }); // AddDbContext registers ScadaLinkDbContext via EF's activator, which only injects // DbContextOptions. Override that registration (last registration wins for resolution) // with a factory that also supplies the IDataProtectionProvider, so the encrypting // value converter for secret columns is always wired up at runtime. services.AddScoped(serviceProvider => { var options = serviceProvider.GetRequiredService>(); var protectionProvider = serviceProvider.GetRequiredService(); return new ScadaLinkDbContext(options, protectionProvider); }); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddDataProtection() .PersistKeysToDbContext(); return services; } /// /// Obsolete parameterless overload. This previously registered nothing, which meant a /// central node wired up with it failed late and opaquely — the first repository /// resolution threw a DI exception far from the actual misconfiguration. Use /// and pass the /// configured connection string. /// /// /// Always thrown. The connection string is required; there is no valid no-op registration. /// [Obsolete( "AddConfigurationDatabase() with no connection string registers nothing and is not a " + "valid configuration. Call AddConfigurationDatabase(connectionString) instead.", error: true)] public static IServiceCollection AddConfigurationDatabase(this IServiceCollection services) { // Defence-in-depth: even if a caller suppresses the compile-time obsolete error, // fail fast at wire-up time rather than silently registering nothing and surfacing // an opaque DI resolution failure much later. throw new InvalidOperationException( "AddConfigurationDatabase() requires a connection string. Call " + "AddConfigurationDatabase(connectionString) with the configured " + "'ScadaLink:Database:ConfigurationDb' value."); } }