Skip to content

BaseTemplate DI Extensibility

Overview

ConnectSoft.BaseTemplate provides a default DI and middleware orchestration pipeline that can be reused and specialized by higher-layer templates (for example Identity).

The extensibility model is based on:

  • MicroserviceRegistrationExtensions as entry points
  • DefaultMicroserviceRegistration for unchanged default behavior
  • MicroserviceRegistrationBase protected virtual hooks for targeted customization

This approach reduces duplicated startup/DI orchestration code across repositories while keeping extension points explicit.

Compile-time vs runtime: DI hooks control runtime service registration. MSBuild flags in Directory.Build.props (and optional DisableMicrosoftExtensionsStackForMinimalHost.props in extended repos) control which optional stacks and ConnectSoft.Extensions.* consumers are active at build time. See Template layering and reuse — MSBuild layering and ConnectSoft.Extensions catalog.

Terminology used in this guide:

  • base + add
  • replace whole step
  • default-preserving hooks
  • submodule refresh

Architecture Contract

At startup, registration calls delegate to a registration class instead of hardcoding all logic in static extension methods:

  • ConfigureMicroserviceServices(...)
  • UseMicroserviceServices(...)

Default flow remains in BaseTemplate through DefaultMicroserviceRegistration. Derived templates extend by inheriting from MicroserviceRegistrationBase.

Hook Usage Patterns

Additive (base + add)

Use this when BaseTemplate behavior should stay intact and template-specific registrations are appended.

Typical examples:

  • additional localization resources
  • named/typed HTTP clients
  • template-specific metrics

Full Step Replacement (replace whole step)

Use this when a template must alter the full registration step.

Typical examples:

  • health checks subset/scope
  • problem details strategy
  • migrations assembly selection

Common Hook Areas

MicroserviceRegistrationBase organizes hooks by stage, including:

  • domain model registration stages
  • localization, HTTP clients, and metrics
  • service discovery, redaction, rate limiting, and web security
  • API stack (problem details, REST infrastructure, Swagger, gRPC)
  • persistence and migrations (GetMigrationAssembly, migrator registration)
  • health checks and validation
  • endpoint mapping and shutdown stages

DI Hook Reference

The table below lists the most commonly overridden hooks from MicroserviceRegistrationBase. All web-host extended templates (Identity, Auth Server, Microservice, Bot Framework, Health Checks Aggregator, API Gateway) follow the three-layer DI chain ApplicationModelRegistrationBaseMicroserviceRegistrationBase → template-specific registration. The Worker template uses a simpler pattern based on the generic host and does not inherit from MicroserviceRegistrationBase.

Hook Responsibility When to Override
RegisterOptions Binds IOptions<T> / IConfigureOptions<T> from configuration sections. Base registers shared infrastructure options from the "BaseTemplate" config section. Override with replace strategy (do NOT call base). Each extended template has its own root options section name (e.g., "Microservice", "ApiGateway") and its own OptionsExtensions.AddMicroserviceOptions() that registers all infrastructure options plus template-specific bindings.
RegisterDomainModelServices Registers domain-layer services such as repositories, domain services, and aggregate factories via the Application Model scanning conventions. Override when the template introduces its own domain model assembly or needs to register domain services that the base scanning does not cover.
ConfigureServicesAfterDomainModel Runs after domain model registration completes. Intended for services that depend on domain registrations being present in the container. Override to wire up cross-cutting concerns that depend on domain types — for example, decorators, domain event dispatchers, or saga orchestrators.
RegisterTemplateMetrics Registers OpenTelemetry Meter instances and custom metric instruments for the template. Base registers shared infrastructure meters. Override to add template-specific meters and counters (e.g., IdentityAuthenticationMetrics, GatewayRoutingMetrics).
MapMicroserviceEndpoints Maps HTTP endpoints (minimal APIs, controllers, gRPC services) onto the request pipeline after middleware is configured. Override to map template-specific endpoints. Call base first to keep shared health-check and diagnostics endpoints, then add domain routes.
UseSecurityMiddleware Configures authentication, authorization, CORS, and rate-limiting middleware in the request pipeline. Override when the template requires a different auth scheme (e.g., Auth Server's OpenIddict validation pipeline) or needs to alter CORS / rate-limiting policies. Always call base unless fully replacing the security stack.
ConfigureAdditionalServices Catch-all hook that runs at the end of the service-registration phase. Receives the full IServiceCollection. Override for any registration that does not fit the earlier, more specific hooks — for example, hosted background services, named HttpClient registrations, or third-party SDK setup.

Extended Template Registration Classes

Each extended template provides a single registration coordinator class that inherits from MicroserviceRegistrationBase and overrides only the hooks where template-specific behavior is needed. All other DI orchestration is inherited from the base.

Template Registration Class Key Overrides
Identity IdentityMicroserviceRegistration RegisterOptions, RegisterAutoMapper, RegisterPersistenceModel, RegisterFluentValidation, RegisterGrpcInfrastructure, RegisterHealthChecks, RegisterHostedServices, RegisterOpenTelemetry, RegisterHttpClients, RegisterMessaging, RegisterProblemDetails, RegisterWebApiInfrastructure, RegisterSwagger, RegisterDomainModelServices, ConfigureServicesAfterDomainModel, RegisterTemplateMetrics, MapMicroserviceEndpoints
Auth Server AuthServerMicroserviceRegistration RegisterOptions, RegisterAutoMapper, RegisterPersistenceModel, RegisterFluentValidation, RegisterHealthChecks, RegisterHostedServices, RegisterOpenTelemetry, RegisterHttpClients, RegisterMessaging, RegisterProblemDetails, RegisterWebApiInfrastructure, RegisterSwagger, RegisterDomainModelServices, ConfigureServicesAfterDomainModel, RegisterTemplateMetrics, MapMicroserviceEndpoints
Bot Framework BotFrameworkMicroserviceRegistration RegisterOptions, RegisterAutoMapper, RegisterPersistenceModel, RegisterFluentValidation, RegisterGrpcInfrastructure, RegisterHealthChecks, RegisterHostedServices, RegisterOpenTelemetry, RegisterHttpClients, RegisterMessaging, RegisterProblemDetails, RegisterWebApiInfrastructure, RegisterSwagger, RegisterDomainModelServices, ConfigureServicesAfterDomainModel, RegisterTemplateMetrics, MapMicroserviceEndpoints
API Gateway ApiGatewayMicroserviceRegistration RegisterOptions, RegisterAutoMapper, RegisterFluentValidation, RegisterHealthChecks, RegisterHostedServices, RegisterOpenTelemetry, RegisterHttpClients, RegisterRedaction, RegisterProblemDetails, RegisterWebApiInfrastructure, RegisterSwagger, RegisterDomainModelServices, RegisterTemplateMetrics, MapMicroserviceEndpoints
Health Checks Aggregator HealthChecksAggregatorRegistration RegisterOptions, RegisterHealthChecks, RegisterHostedServices, RegisterOpenTelemetry, RegisterHttpClients, RegisterProblemDetails, RegisterWebApiInfrastructure, RegisterDomainModelServices, RegisterTemplateMetrics, MapMicroserviceEndpoints
Microservice Template MicroserviceTemplateRegistration RegisterOptions, RegisterAutoMapper, RegisterPersistenceModel, RegisterFluentValidation, RegisterGrpcInfrastructure, RegisterHealthChecks, RegisterHostedServices, RegisterOpenTelemetry, RegisterHttpClients, RegisterMessaging, RegisterProblemDetails, RegisterWebApiInfrastructure, RegisterSwagger, RegisterDomainModelServices, ConfigureServicesAfterDomainModel, RegisterTemplateMetrics, MapMicroserviceEndpoints

Override Strategy

Each override typically follows one of two strategies:

  • Replace: The override directly calls the template's own static helper method (e.g., AutoMapperExtensions.AddMicroserviceAutoMapper(services)) without calling base. Used when the template's method fully covers all needed registrations and calling both would produce duplicates.
  • Base + Add: The override calls base.RegisterXxx(...) first to retain shared infrastructure registrations, then appends template-specific registrations. Used for RegisterOptions and MapMicroserviceEndpoints in most templates.

Duplicate Extension Cleanup

During alignment, infrastructure-only extension files that were exact duplicates of base template functionality were identified and removed from extended templates. These included:

  • AzureAppConfigurationExtensions, AzureApplicationInsightsExtensions, CompressionExtensions
  • FeatureFlagsExtensions, HttpLoggingExtensions, MicroserviceRedactionExtensions
  • LatencyTelemetryCollectionExtensions, RateLimitingExtensions, RequestTimeoutExtensions
  • ResourceMonitoringExtensions, SerilogLoggingExtensions, ServiceDiscoveryExtensions
  • HeaderPropagationExtensions, WebSecurityExtensions, LocalizationExtensions

These registrations are now handled by MicroserviceRegistrationBase hooks in the base template.

OpenTelemetryAttributeName Consolidation

OpenTelemetryAttributeName.cs was removed from all extended templates. The type is sourced from the ConnectSoft.Extensions.Observability NuGet package. Extended templates reference it via using ConnectSoft.Extensions.Observability; in their OpenTelemetryExtensions.cs files.

Program.cs Serilog Bootstrap Alignment

All extended templates use the modern Serilog bootstrap pattern from ConnectSoft.Extensions.Logging.Serilog:

#if Serilog
Log.Logger = SerilogBootstrapExtensions.CreateBootstrapLogger();
try
{
#endif
    // ... host setup ...
#if Serilog
}
catch (Exception exception)
{
    Log.Fatal(exception, "Application failed to start");
    throw;
}
finally
{
    await Log.CloseAndFlushAsync().ConfigureAwait(false);
}
#endif

The host builder uses .UseSerilogBootstrap() instead of .UseSerilog(...) to leverage the bootstrap logger upgrade pattern. All Logger.Information(...) calls use the static Log.Information(...) API from Serilog.

Azure App Configuration Null-Guard

All Program.cs and test BeforeAfterTestRunHooks.cs files include a null-guard before options.Connect():

string? azureAppConfigurationConnectionString = configurationRoot.GetConnectionString("AzureAppConfiguration");
if (string.IsNullOrWhiteSpace(azureAppConfigurationConnectionString))
{
    throw new InvalidOperationException(
        "Azure App Configuration is enabled but the connection string is missing. " +
        "Set `ConnectionStrings:AzureAppConfiguration` in configuration.");
}
options.Connect(azureAppConfigurationConnectionString)

Migration Guide (from duplicated DI code)

  1. Keep BaseTemplate defaults as the source of truth.
  2. Identify duplicated DI orchestration in derived template repos.
  3. Move template-specific behavior into MicroserviceRegistrationBase overrides.
  4. Remove mostly identical orchestration code from derived templates.
  5. Remove local copies of types available from shared NuGet packages (e.g., OpenTelemetryAttributeName from ConnectSoft.Extensions.Observability).
  6. Align Program.cs to use SerilogBootstrapExtensions.CreateBootstrapLogger() with #if Serilog guards and .UseSerilogBootstrap().
  7. Add Azure App Configuration null-guard in both Program.cs and test BeforeAfterTestRunHooks.cs.
  8. Add HealthCheckPublisherOptions speed-up (1s delay, 2s period) in TestStartup.ConfigureServices.
  9. Respect submodule boundaries: refresh submodule to consume BaseTemplate changes; do not edit submodule contents directly.
  10. Validate by building and running startup smoke checks (endpoints, health checks, and middleware behavior).