From 0a4daccc0f6a7276c7deba92b6a471ac1acbb3db Mon Sep 17 00:00:00 2001 From: TekH Date: Thu, 28 May 2026 21:57:03 +0200 Subject: [PATCH] Refactor service registration for modularity Introduced `EnvelopeGeneratorOptions` and `SqlCacheOptions` to enable fine-grained control over optional service registrations in `AddEnvelopeGenerator`. Updated `AddEnvelopeGenerator` to conditionally register services like `HttpContextAccessor`, `DistributedSqlServerCache`, `Dispatcher`, `MemoryCache`, and `UserManager` based on these options. Updated `DependencyInjection.csproj` to include necessary framework references and package dependencies. Simplified `Program.cs` by consolidating service registrations into `AddEnvelopeGenerator`, reducing boilerplate and improving maintainability. Improved extensibility by centralizing service registration logic, allowing consuming projects to customize configurations without modifying the core library. Updated documentation and removed unused directives. --- .../DependencyInjection.cs | 123 ++++++++++++------ ...velopeGenerator.DependencyInjection.csproj | 19 +++ EnvelopeGenerator.Web/Program.cs | 48 +++---- 3 files changed, 119 insertions(+), 71 deletions(-) diff --git a/EnvelopeGenerator.DependencyInjection/DependencyInjection.cs b/EnvelopeGenerator.DependencyInjection/DependencyInjection.cs index cebe6df9..8bbbc853 100644 --- a/EnvelopeGenerator.DependencyInjection/DependencyInjection.cs +++ b/EnvelopeGenerator.DependencyInjection/DependencyInjection.cs @@ -4,12 +4,59 @@ using EnvelopeGenerator.Application; using EnvelopeGenerator.Application.Common.Interfaces.Services; using EnvelopeGenerator.Application.Services; using EnvelopeGenerator.Infrastructure; +using DigitalData.EmailProfilerDispatcher; +using DigitalData.UserManager.DependencyInjection; namespace EnvelopeGenerator.DependencyInjection; +/// +/// Controls which optional services are registered by . +/// All flags default to true. Set a flag to false if the consuming project +/// already registers that service itself or simply does not need it. +/// +public sealed class EnvelopeGeneratorOptions +{ + /// Calls AddHttpContextAccessor(). Default: true. + public bool AddHttpContextAccessor { get; set; } = true; + + /// + /// Calls AddDistributedSqlServerCache() with the supplied . + /// Requires to be configured. Default: true. + /// + public bool AddDistributedSqlServerCache { get; set; } = true; + + /// + /// Options for the distributed SQL Server cache. + /// Required when is true. + /// + public SqlCacheOptions? SqlCacheOptions { get; set; } + + /// Calls AddDispatcher<TDbContext>(). Default: true. + public bool AddDispatcher { get; set; } = true; + + /// Calls AddMemoryCache(). Default: true. + public bool AddMemoryCache { get; set; } = true; + + /// Calls AddUserManager<TDbContext>(). Default: true. + public bool AddUserManager { get; set; } = true; +} + +/// Options for AddDistributedSqlServerCache. +public sealed class SqlCacheOptions +{ + /// SQL Server connection string. + public string ConnectionString { get; set; } = string.Empty; + + /// Schema name. Default: dbo. + public string SchemaName { get; set; } = "dbo"; + + /// Table name. Default: TBDD_CACHE. + public string TableName { get; set; } = "TBDD_CACHE"; +} + /// /// Extension methods for registering EnvelopeGenerator services into an . -/// Use as the single entry-point for projects that need both the +/// Use as the single entry-point for projects that need both the /// application layer (MediatR, AutoMapper, CRUD services, configuration sections) and the infrastructure /// layer (repositories, DbContext, SQL executors). /// For projects that do not need a database (e.g. lightweight API gateways or unit-test hosts), use @@ -18,61 +65,56 @@ namespace EnvelopeGenerator.DependencyInjection; public static class DependencyInjection { /// - /// Registers the full EnvelopeGenerator stack – application and infrastructure services – into - /// the provided . - /// - /// Internally this calls AddEnvelopeGeneratorServices (application layer) and - /// AddEnvelopeGeneratorInfrastructureServices (infrastructure layer). - /// A and / or DbTriggerParams must be - /// configured through ; without it no database connection will - /// be established at runtime. - /// + /// Registers the full EnvelopeGenerator stack using as the DbContext type. /// - /// Service collection to register services into. - /// - /// Application configuration. Used to bind DispatcherParams, MailParams, - /// AuthenticatorParams, TotpSmsParams, GtxMessagingParams and other - /// application-level option sections. - /// - /// - /// Optional callback to configure the infrastructure layer registration. - /// Typical usage: - /// - /// services.AddEnvelopeGenerator(config, opt => - /// { - /// opt.AddDbContext(o => o.UseSqlServer(connectionString)); - /// opt.AddDbTriggerParams(config); - /// }); - /// - /// - /// The updated . -#pragma warning disable CS0618 // AddEnvelopeGeneratorServices / AddEnvelopeGeneratorInfrastructureServices are intentionally wrapped here public static IServiceCollection AddEnvelopeGenerator( this IServiceCollection services, IConfiguration configuration, - Action? infrastructureOptions = null) + Action? infrastructureOptions = null, + Action? options = null) { + var opt = new EnvelopeGeneratorOptions(); + options?.Invoke(opt); + +#pragma warning disable CS0618 // Application layer: CRUD services, MediatR, AutoMapper, configuration sections. services.AddEnvelopeGeneratorServices(configuration); // Infrastructure layer: repositories, DbContext, Dapper type maps, SQL executors. - services.AddEnvelopeGeneratorInfrastructureServices(opt => + services.AddEnvelopeGeneratorInfrastructureServices(cfg => { - infrastructureOptions?.Invoke(opt); + infrastructureOptions?.Invoke(cfg); }); +#pragma warning restore CS0618 + + if (opt.AddHttpContextAccessor) + services.AddHttpContextAccessor(); + + if (opt.AddDistributedSqlServerCache && opt.SqlCacheOptions is { } cacheOpts) + services.AddDistributedSqlServerCache(o => + { + o.ConnectionString = cacheOpts.ConnectionString; + o.SchemaName = cacheOpts.SchemaName; + o.TableName = cacheOpts.TableName; + }); + + if (opt.AddDispatcher) + services.AddDispatcher(); + + if (opt.AddMemoryCache) + services.AddMemoryCache(); + +#pragma warning disable CS0618 + if (opt.AddUserManager) + services.AddUserManager(); +#pragma warning restore CS0618 return services; } -#pragma warning restore CS0618 /// /// Registers only the application layer services (MediatR handlers, AutoMapper profiles, /// CRUD services, configuration sections) without any infrastructure / database dependencies. - /// - /// Useful for projects that already manage their own DbContext or do not require direct database - /// access, such as lightweight API gateways, console tools or unit/integration test hosts that - /// use an in-memory database configured elsewhere. - /// /// /// Service collection to register services into. /// Application configuration used to bind application-level option sections. @@ -90,11 +132,6 @@ public static class DependencyInjection /// /// Registers as the scoped /// implementation. - /// - /// Call this in addition to when the consuming project needs to - /// send envelope e-mails directly (e.g. a Worker Service or the Web project). Projects that rely - /// purely on MediatR commands to trigger mail delivery do not need to call this. - /// /// /// Service collection to register services into. /// The updated . diff --git a/EnvelopeGenerator.DependencyInjection/EnvelopeGenerator.DependencyInjection.csproj b/EnvelopeGenerator.DependencyInjection/EnvelopeGenerator.DependencyInjection.csproj index e1df3bc2..96e2398e 100644 --- a/EnvelopeGenerator.DependencyInjection/EnvelopeGenerator.DependencyInjection.csproj +++ b/EnvelopeGenerator.DependencyInjection/EnvelopeGenerator.DependencyInjection.csproj @@ -25,9 +25,28 @@ false + + + + + + + + + + + + + + + + + + + diff --git a/EnvelopeGenerator.Web/Program.cs b/EnvelopeGenerator.Web/Program.cs index c4b2b521..f25a9888 100644 --- a/EnvelopeGenerator.Web/Program.cs +++ b/EnvelopeGenerator.Web/Program.cs @@ -12,7 +12,6 @@ using DigitalData.EmailProfilerDispatcher; using EnvelopeGenerator.Infrastructure; using EnvelopeGenerator.Web.Sanitizers; using EnvelopeGenerator.Web.Models.Annotation; -using DigitalData.UserManager.DependencyInjection; using EnvelopeGenerator.Web.Middleware; using EnvelopeGenerator.DependencyInjection; using EnvelopeGenerator.Web; @@ -55,8 +54,6 @@ try }); }); - builder.Services.AddHttpContextAccessor(); - builder.ConfigureBySection(); // Add controllers and razor views @@ -93,26 +90,29 @@ try var connStr = config.GetConnectionString(cnnStrName) ?? throw new InvalidOperationException($"Connection string '{cnnStrName}' is missing in the application configuration."); - builder.Services.AddDistributedSqlServerCache(options => - { - options.ConnectionString = connStr; - options.SchemaName = "dbo"; - options.TableName = "TBDD_CACHE"; - }); - // Add envelope generator services - builder.Services.AddEnvelopeGenerator(config, opt => - { - opt.AddDbTriggerParams(config); - opt.AddDbContext((provider, options) => + builder.Services.AddEnvelopeGenerator(config, + infrastructureOptions: opt => { - var logger = provider.GetRequiredService>(); - options.UseSqlServer(connStr) - .LogTo(log => logger.LogInformation("{log}", log), Microsoft.Extensions.Logging.LogLevel.Trace) - .EnableSensitiveDataLogging() - .EnableDetailedErrors(); + opt.AddDbTriggerParams(config); + opt.AddDbContext((provider, options) => + { + var logger = provider.GetRequiredService>(); + options.UseSqlServer(connStr) + .LogTo(log => logger.LogInformation("{log}", log), Microsoft.Extensions.Logging.LogLevel.Trace) + .EnableSensitiveDataLogging() + .EnableDetailedErrors(); + }); + }, + options: opt => + { + opt.SqlCacheOptions = new() + { + ConnectionString = connStr, + SchemaName = "dbo", + TableName = "TBDD_CACHE" + }; }); - }); builder.Services.Configure(options => { @@ -162,18 +162,10 @@ try // Register mail services builder.Services.AddEnvelopeMailService(); - builder.Services.AddDispatcher(); - - builder.Services.AddMemoryCache(); - builder.ConfigureBySection(); builder.ConfigureBySection(); -#pragma warning disable CS0618 // Type or member is obsolete - builder.Services.AddUserManager(); -#pragma warning restore CS0618 // Type or member is obsolete - var app = builder.Build(); app.UseMiddleware();