Skip to content

Extensibility Guide

This document explains how ConnectSoft templates enable extensibility through extension point patterns. It is written for architects and engineers who need to add domain-specific metrics, configuration options, or testing infrastructure to specialized templates without modifying the base template.

ConnectSoft's extensibility model ensures that base infrastructure remains domain-agnostic while allowing specialized templates to add their own metrics, options, and test patterns through well-defined extension points.

For startup orchestration and DI registration hooks, see BaseTemplate DI Extensibility (three layers: ApplicationModelRegistrationBase from NuGet, MicroserviceRegistrationBase in the base-template submodule, then template-specific registration).

Important

Base infrastructure never contains domain-specific logic. All domain-specific functionality (metrics, options, tests) is added through extension points implemented in specialized templates.

Extension Point Philosophy

ConnectSoft uses a plugin-style architecture where:

  1. Base provides infrastructure + extension points (interfaces, abstract classes, registration hooks)
  2. Specialized templates implement extension points (concrete implementations)
  3. Base discovers and registers implementations (via scanning or explicit registration)

Migration Checklist (DI Orchestration)

When migrating a specialized template from duplicated registration code to hook-based extensibility:

  1. Identify duplicated DI orchestration in the specialized ApplicationModel.
  2. Keep BaseTemplate default registration as the baseline.
  3. Move specialized behavior to override hooks in template-specific registration (derived from MicroserviceRegistrationBase; see BaseTemplate DI Extensibility).
  4. Remove mostly identical orchestration code from specialized template.
  5. Do not edit submodule content directly; refresh submodule to consume BaseTemplate updates.
  6. Verify startup behavior with build and smoke tests.
flowchart TB
    subgraph Base["Base Template"]
        INFRA[Infrastructure]
        EXT[Extension Points<br/>IMetricsFeature<br/>IConfigureOptions]
        REG[Registration<br/>Auto-discovery]
    end

    subgraph Identity["Identity Template"]
        IMPL1[IdentityMetricsFeature<br/>implements IMetricsFeature]
        IMPL2[ConfigureIdentityOptions<br/>implements IConfigureOptions]
    end

    INFRA --> EXT
    EXT --> REG
    IMPL1 -->|Implements| EXT
    IMPL2 -->|Implements| EXT
    REG -->|Discovers| IMPL1
    REG -->|Discovers| IMPL2

    style Base fill:#BBDEFB
    style Identity fill:#C8E6C9
    style EXT fill:#FFE0B2
Hold "Alt" / "Option" to enable pan & zoom

Metrics Infrastructure and IMetricsFeature

Base Metrics Infrastructure

The base template provides metrics infrastructure through ConnectSoft.Extensions.Metrics:

Base Setup:

// In ConnectSoft.Extensions.Metrics
public interface IMetricsFeature
{
    void Register(IMeterFactory meterFactory);
}

public static class MetricsServiceCollectionExtensions
{
    public static IServiceCollection AddConnectSoftMetrics(
        this IServiceCollection services)
    {
        // Setup OpenTelemetry / Meter infrastructure
        services.AddOpenTelemetryMetering();

        // Auto-discover and register all IMetricsFeature implementations
        services.Scan(scan => scan
            .FromApplicationDependencies()
            .AddClasses(c => c.AssignableTo<IMetricsFeature>())
            .AsImplementedInterfaces()
            .WithSingletonLifetime());

        // Bootstrapper that calls Register() on all features
        services.AddHostedService<MetricsFeatureBootstrapper>();

        return services;
    }
}

Registration in Base Host:

// In base template Program.cs
builder.Services.AddConnectSoftMetrics();

Domain-Specific Metrics Implementation

Specialized templates implement IMetricsFeature to add domain-specific metrics:

Identity Metrics Example:

// In Identity template: Identity.Infrastructure/Metrics/IdentityMetricsFeature.cs
using ConnectSoft.Extensions.Metrics;

namespace Identity.Infrastructure.Metrics;

public sealed class IdentityMetricsFeature : IMetricsFeature
{
    private readonly Meter _meter;
    private readonly Counter<long> _loginSuccessCounter;
    private readonly Counter<long> _loginFailedCounter;

    public IdentityMetricsFeature(IMeterFactory meterFactory)
    {
        _meter = meterFactory.Create("ConnectSoft.Identity");

        _loginSuccessCounter = _meter.CreateCounter<long>(
            "identity.login.success",
            "count",
            "Number of successful login attempts");

        _loginFailedCounter = _meter.CreateCounter<long>(
            "identity.login.failed",
            "count",
            "Number of failed login attempts");
    }

    public void Register(IMeterFactory meterFactory)
    {
        // Registration happens automatically via DI
    }

    // Domain-specific methods
    public void OnLoginSuccess()
    {
        _loginSuccessCounter.Add(1);
    }

    public void OnLoginFailed(string reason)
    {
        _loginFailedCounter.Add(1, new KeyValuePair<string, object?>("reason", reason));
    }
}

Registration in Identity Infrastructure:

// In Identity.Infrastructure/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddConnectSoftIdentityInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // Register Identity metrics feature
        services.AddSingleton<IMetricsFeature, IdentityMetricsFeature>();

        // ... other Identity infrastructure registration

        return services;
    }
}

Options Infrastructure and IOptions

Base Options Infrastructure

The base template provides options infrastructure through ConnectSoft.Extensions.Options:

Base Setup:

// In ConnectSoft.Extensions.Options
public static class OptionsServiceCollectionExtensions
{
    public static IServiceCollection AddConnectSoftOptions(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // Bind generic options
        services.Configure<ObservabilityOptions>(
            configuration.GetSection("ConnectSoft:Observability"));
        services.Configure<MessagingOptions>(
            configuration.GetSection("ConnectSoft:Messaging"));

        // Auto-discover and register all IConfigureOptions<T> implementations
        services.Scan(scan => scan
            .FromApplicationDependencies()
            .AddClasses(c => c.AssignableTo(typeof(IConfigureOptions<>)))
            .AsImplementedInterfaces()
            .WithTransientLifetime());

        return services;
    }
}

Domain-Specific Options Implementation

Specialized templates define their own options and configure them:

Identity Options Example:

// In Identity template: ConnectSoft.IdentityTemplate.Options (or options under domain)
namespace ConnectSoft.IdentityTemplate.Options;

public sealed class IdentitySecurityOptions
{
    public const string SectionName = "ConnectSoft:Identity:Security";

    public bool RequireConfirmedEmail { get; set; } = true;
    public int MaxFailedAccessAttempts { get; set; } = 5;
    public TimeSpan LockoutTimeSpan { get; set; } = TimeSpan.FromMinutes(15);
    public int PasswordMinLength { get; set; } = 8;
}

Options Configuration:

// In Identity template: Identity.Infrastructure/Options/ConfigureIdentitySecurityOptions.cs
using Microsoft.Extensions.Options;

namespace Identity.Infrastructure.Options;

public sealed class ConfigureIdentitySecurityOptions 
    : IConfigureOptions<IdentitySecurityOptions>
{
    private readonly IConfiguration _configuration;

    public ConfigureIdentitySecurityOptions(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void Configure(IdentitySecurityOptions options)
    {
        _configuration.GetSection(IdentitySecurityOptions.SectionName).Bind(options);
    }
}

Base Testing Infrastructure

The base template provides reusable testing infrastructure that specialized templates can leverage.

ITestAppFactory Interface

Base Interface:

// In Base.Testing.Infrastructure/ITestAppFactory.cs
namespace Base.Testing.Infrastructure;

public interface ITestAppFactory
{
    HttpClient CreateClient();
    IServiceProvider Services { get; }
    void ConfigureWebHost(Action<IWebHostBuilder> configure);
}

AcceptanceTestBase

Base Test Class:

// In Base.Testing.Infrastructure/AcceptanceTestBase.cs
namespace Base.Testing.Infrastructure;

[TestClass]
public abstract class AcceptanceTestBase
{
    protected abstract ITestAppFactory AppFactory { get; }

    [TestMethod]
    public async Task Health_endpoint_returns_ok()
    {
        var client = AppFactory.CreateClient();
        var response = await client.GetAsync("/health");
        response.EnsureSuccessStatusCode();
    }
}

Identity Testing Strategy

Specialized templates implement the base test infrastructure for their domain.

IdentityTestAppFactory

Identity Factory Implementation:

// In Identity.AcceptanceTests/IdentityTestAppFactory.cs
using Base.Testing.Infrastructure;
using Microsoft.AspNetCore.Mvc.Testing;

namespace Identity.AcceptanceTests;

public sealed class IdentityTestAppFactory : ITestAppFactory
{
    private WebApplicationFactory<Program>? _appFactory;

    public IServiceProvider Services => _appFactory?.Services 
        ?? throw new InvalidOperationException("Factory not initialized");

    public IdentityTestAppFactory()
    {
        _appFactory = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder =>
            {
                // Override configuration for tests
                builder.ConfigureAppConfiguration((context, config) =>
                {
                    config.AddInMemoryCollection(new Dictionary<string, string?>
                    {
                        ["ConnectionStrings:DefaultConnection"] = 
                            "Server=(localdb)\\mssqllocaldb;Database=IdentityTest;Trusted_Connection=true"
                    });
                });
            });
    }

    public HttpClient CreateClient()
    {
        return _appFactory?.CreateClient() 
            ?? throw new InvalidOperationException("Factory not initialized");
    }

    public void ConfigureWebHost(Action<IWebHostBuilder> configure)
    {
        _appFactory = _appFactory?.WithWebHostBuilder(configure) 
            ?? throw new InvalidOperationException("Factory not initialized");
    }
}

Identity Acceptance Tests

Using Base Test Infrastructure:

// In Identity.AcceptanceTests/IdentityHealthChecksTests.cs
using Base.Testing.Infrastructure;

namespace Identity.AcceptanceTests;

[TestClass]
public class IdentityHealthChecksTests : AcceptanceTestBase
{
    private static readonly IdentityTestAppFactory Factory = new();

    protected override ITestAppFactory AppFactory => Factory;

    [TestMethod]
    public async Task Identity_health_endpoint_includes_database_check()
    {
        var client = AppFactory.CreateClient();
        var response = await client.GetAsync("/health");
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        Assert.IsTrue(content.Contains("database"));
    }
}

Rules and Anti-Patterns

Do's

Implement extension point interfaces (IMetricsFeature, IConfigureOptions<T>)
Use auto-discovery - Let base infrastructure discover your implementations
Extend base test classes - Reuse AcceptanceTestBase, AggregateTestBase
Keep base domain-agnostic - Never add domain logic to base
Use dependency injection - Register implementations via DI

Don'ts

Don't modify base code - Never add domain-specific code to base template
Don't duplicate base infrastructure - Use base test infrastructure, don't copy it
Don't bypass extension points - Use interfaces, don't modify base directly
Don't hard-code domain logic - Use options pattern for configuration
Don't create base dependencies on domains - Base should not reference Identity/Audit/etc.

Extension Point Summary

Extension Point Interface/Pattern Purpose Example
Metrics IMetricsFeature Add domain-specific metrics IdentityMetricsFeature
Options IConfigureOptions<T> Configure domain-specific options ConfigureIdentitySecurityOptions
Testing ITestAppFactory Create test HTTP clients IdentityTestAppFactory
Testing AcceptanceTestBase Reuse acceptance test patterns IdentityHealthChecksTests