Skip to content

Application Model in ConnectSoft Templates

Purpose & Overview

The Application Model is the central orchestration layer of ConnectSoft Templates (Microservice, API Gateway, Identity, Auth Server, etc.). It provides a comprehensive set of extension methods for dependency injection registration, middleware pipeline configuration, and cross-cutting concerns integration. The ApplicationModel serves as the composition root that wires together all layers and features of the application into a cohesive, production-ready solution.

Why Application Model?

The Application Model offers several key benefits:

  • Centralized Configuration: Single entry point for all service registrations and middleware setup
  • Clean Separation: Keeps Application project minimal—all orchestration logic in ApplicationModel
  • Consistency: Standardized patterns for registering services and configuring middleware
  • Modularity: Feature-based extension methods enable optional feature inclusion
  • Testability: Easy to override and test individual components
  • Maintainability: Clear organization makes it easy to understand and modify
  • Conditional Compilation: Feature flags enable optional feature inclusion at compile time

Application Model Philosophy

The ApplicationModel is the composition root of ConnectSoft templates. It orchestrates all layers, features, and cross-cutting concerns without containing business logic. This separation ensures the Application project remains minimal and focused on hosting, while ApplicationModel handles all the complexity of wiring components together.

Architecture Overview

Application Model Position in Clean Architecture

Application (Host)
    ├── Program.cs (Entry Point)
    └── Startup.cs (Minimal - delegates to ApplicationModel)
ApplicationModel (Orchestration Layer)
    ├── DI Registration (ConfigureServices)
    ├── Middleware Pipeline (Configure)
    └── Endpoint Mapping
All Layers
    ├── DomainModel
    ├── PersistenceModel
    ├── MessagingModel
    ├── ServiceModel
    └── Cross-Cutting Concerns

Project Structure

ConnectSoft.{TemplateName}.ApplicationModel/
├── {TemplateName}RegistrationExtensions.cs (Main orchestration)
├── OptionsExtensions.cs (Configuration options)
├── DomainModelExtensions.cs (Domain layer registration)
├── PersistenceModelExtensions.cs (Persistence registration)
├── WebApiExtensions.cs (REST API configuration)
├── GrpcExtensions.cs (gRPC configuration)
├── SignalRExtensions.cs (SignalR configuration)
├── SwaggerExtensions.cs (API documentation)
├── HealthChecksExtensions.cs (Health checks)
├── MassTransitExtensions.cs (Messaging)
├── NServiceBusExtensions.cs (Messaging)
├── OrleansExtensions.cs (Actor model)
├── NHibernateExtensions.cs (ORM)
├── MongoDbExtensions.cs (NoSQL)
├── HangFireExtensions.cs (Background jobs)
├── OpenTelemetryExtensions.cs (Observability)
├── AzureApplicationInsightsExtensions.cs (Monitoring)
├── StartupWarmupGate.cs (Startup coordination)
├── HostedServicesExtensions.cs (Background services)
└── ... (other feature extensions)

Core Components

1. Registration Extensions

The main orchestration class that coordinates all service registrations and middleware configuration.

ConfigureServices

Purpose: Register all services in the dependency injection container.

Usage:

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.ConfigureApplicationServices(this.Configuration, this.WebHostEnvironment);
}

What it registers:

  • Configuration options (Options Pattern)
  • Hosted services (background workers, warmup gate)
  • Domain model services (use cases, domain services)
  • Persistence model (repositories, unit of work)
  • Service models (REST, gRPC, GraphQL, SignalR)
  • Messaging (MassTransit, NServiceBus)
  • Actor model (Orleans, Dapr, Akka)
  • Background jobs (Hangfire, Quartz)
  • Observability (OpenTelemetry, Application Insights)
  • Health checks
  • Feature flags
  • Caching
  • Rate limiting
  • And more...

Key Features:

  • Conditional compilation directives (#if) for optional features
  • Order-dependent registration (options first, then services using options)
  • Validation and error handling
  • Centralized service lifetime management

UseApplicationServices

Purpose: Configure the HTTP request pipeline and middleware.

Usage:

// Startup.cs
public void Configure(IApplicationBuilder app, ...)
{
    app.UseApplicationServices(this.Configuration, environment, loggerFactory, hostApplicationLifetime);
}

Middleware Pipeline Order:

  1. Problem Details (error handling)
  2. Forwarded Headers (reverse proxy)
  3. Logging (Log4Net, NHibernate, NServiceBus)
  4. Exception Handling (Developer/Production)
  5. HSTS (HTTPS enforcement)
  6. Azure App Configuration (dynamic config)
  7. Conversation ID (correlation)
  8. HTTP Logging
  9. Latency Telemetry
  10. Static Files
  11. WebSockets
  12. Service Models (REST, gRPC, CoreWCF)
  13. CORS
  14. HTTPS Redirection
  15. Routing
  16. Rate Limiting
  17. Localization
  18. Header Propagation
  19. Swagger (API documentation)
  20. Hangfire (background jobs)
  21. OpenTelemetry (observability)
  22. Orleans Dashboard
  23. Endpoints (controllers, health checks, etc.)

Key Features:

  • Proper middleware ordering for correct behavior
  • Environment-specific configuration
  • Graceful shutdown handling
  • Startup warmup coordination

2. OptionsExtensions

Purpose: Centralized configuration options registration and access.

Key Features:

  • Validates all options at startup
  • Provides static access to options for extension methods
  • Fail-fast validation prevents misconfigured deployments

Example:

// OptionsExtensions.cs
internal static class OptionsExtensions
{
    internal static ApplicationOptions ApplicationOptions { get; private set; }
    internal static ValidationOptions ValidationOptions { get; private set; }
    // ... other options
}

// Usage in extension methods
services.AddSingleton(new StartupWarmupGate(
    TimeSpan.FromSeconds(OptionsExtensions.ApplicationOptions.StartupWarmupSeconds)));

Registered Options:

  • ApplicationOptions - Core application configuration
  • ValidationOptions - Validation behavior
  • MicroserviceLocalizationOptions - Localization settings
  • ServiceDiscoveryOptions - Service discovery configuration
  • HealthChecksOptions - Health check configuration
  • SwaggerOptions - API documentation settings
  • RateLimitingOptions - Rate limiting configuration
  • MassTransitOptions - Messaging configuration
  • NServiceBusOptions - Messaging configuration
  • OrleansOptions - Actor model configuration
  • And more...

3. DomainModelExtensions

Purpose: Register domain layer services (use cases, domain services).

Implementation:

internal static class DomainModelExtensions
{
    internal static IServiceCollection AddDomainModelServices(this IServiceCollection services)
    {
        // Register TimeProvider (time abstraction)
        services.AddSingleton<TimeProvider>(sp => TimeProvider.System);
        services.ActivateSingleton<TimeProvider>();

        // Register domain services (IDomainService)
        services.Scan(scan => scan
            .FromAssemblyOf<DefaultAggregateRootsRetriever>()
            .AddClasses(classes => classes.AssignableTo(typeof(IDomainService)))
            .AsImplementedInterfaces()
            .WithScopedLifetime());

        // Register use cases (IUseCase<,>)
        services.Scan(scan => scan
            .FromAssemblyOf<DefaultAggregateRootsRetriever>()
            .AddClasses(classes => classes.AssignableTo(typeof(IUseCase<,>)))
            .AsImplementedInterfaces()
            .WithScopedLifetime());

        return services;
    }
}

Key Features:

  • Uses Scrutor for convention-based registration
  • Registers all domain services implementing IDomainService
  • Registers all use cases implementing IUseCase<TInput, TOutput>
  • Scoped lifetime for all domain services

4. PersistenceModelExtensions

Purpose: Register persistence layer services (repositories, unit of work).

Implementation:

internal static class PersistenceModelExtensions
{
    public static IServiceCollection AddPersistenceModel(this IServiceCollection services)
    {
        // If multiple persistence models (NHibernate + MongoDB)
        // Use NHibernate as primary by default
        if (countPersistenceModels > 1)
        {
            services.AddScoped<IAggregateRootsRepository>(
                provider => provider.GetRequiredKeyedService<IAggregateRootsRepository>(
                    ApplicationConstants.NHibernateDIKey));
            // ... other repository interfaces
        }

        return services;
    }
}

Key Features:

  • Supports multiple persistence models (NHibernate + MongoDB)
  • Uses keyed services for multiple implementations
  • Provides primary repository interface when multiple exist

5. StartupWarmupGate

Purpose: Coordinates startup readiness and provides grace period for initialization.

Features:

  • Grace Period: Configurable startup grace period (default: 20 seconds)
  • Manual Readiness: Can be marked ready manually
  • Holds: Supports holding readiness until subsystems are ready
  • Health Check Integration: Used by health checks to delay readiness

Usage:

// Register in DI
services.AddSingleton(new StartupWarmupGate(TimeSpan.FromSeconds(20)));
services.AddHostedService(sp => sp.GetRequiredService<StartupWarmupGate>());

// Use in health checks
public class MyHealthCheck : IHealthCheck
{
    private readonly StartupWarmupGate warmupGate;

    public async Task<HealthCheckResult> CheckHealthAsync(...)
    {
        if (!warmupGate.IsReady)
        {
            return HealthCheckResult.Degraded("Application is still warming up");
        }
        // ... check health
    }
}

// Mark ready after startup
hostApplicationLifetime.ApplicationStarted.Register(() =>
{
    var gate = application.ApplicationServices.GetRequiredService<StartupWarmupGate>();
    gate.MarkReady();
});

API:

  • IsReady: Returns true when ready to serve traffic
  • MarkReady(): Manually mark as ready
  • AcquireHold(): Prevent readiness
  • ReleaseHold(): Allow readiness
  • ExtendGrace(TimeSpan): Extend grace period

DI Registration Patterns

Pattern 1: Feature-Based Extension Methods

Each feature has its own extension method for registration:

// MassTransitExtensions.cs
internal static class MassTransitExtensions
{
    internal static IServiceCollection AddApplicationMassTransit(
        this IServiceCollection services)
    {
        services.AddMassTransit(x =>
        {
            // Configure MassTransit
        });
        return services;
    }
}

// Usage in Registration Extensions
#if UseMassTransit
services.AddApplicationMassTransit();
#endif

Benefits:

  • Clear feature boundaries
  • Easy to enable/disable features
  • Self-contained feature registration
  • Testable in isolation

Pattern 2: Options-First Registration

Options are registered first, then services using those options:

// RegistrationExtensions.cs
public static IServiceCollection ConfigureApplicationServices(...)
{
    // 1. Register options FIRST
    services.AddApplicationOptions(configuration);

    // 2. Register services that use options
    services.AddApplicationHostedServices(); // Uses OptionsExtensions.ApplicationOptions
    services.AddDomainModelServices();
    // ... other services
}

Why:

  • Options must be available before services that depend on them
  • Static access to options via OptionsExtensions properties
  • Fail-fast validation prevents misconfigured deployments

Pattern 3: Convention-Based Registration

Use Scrutor to automatically register services by convention:

// DomainModelExtensions.cs
services.Scan(scan => scan
    .FromAssemblyOf<DefaultAggregateRootsRetriever>()
    .AddClasses(classes => classes.AssignableTo(typeof(IDomainService)))
    .AsImplementedInterfaces()
    .WithScopedLifetime());

Benefits:

  • Reduces boilerplate registration code
  • Automatic discovery of implementations
  • Consistent lifetime management
  • Easy to add new services without registration changes

Pattern 4: Keyed Services for Multiple Implementations

When multiple implementations exist, use keyed services:

// PersistenceModelExtensions.cs
if (countPersistenceModels > 1)
{
    // Use keyed services for multiple implementations
    services.AddScoped<IMicroserviceAggregateRootsRepository>(
        provider => provider.GetRequiredKeyedService<IMicroserviceAggregateRootsRepository>(
            MicroserviceConstants.NHibernateDIKey));
}

Use Cases:

  • Multiple persistence providers (NHibernate + MongoDB)
  • Multiple messaging frameworks (MassTransit + NServiceBus)
  • Feature-specific implementations

Pattern 5: Conditional Registration

Use conditional compilation for optional features:

#if UseMassTransit
services.AddApplicationMassTransit();
#endif

#if UseNServiceBus
services.AddNServiceBus(configuration);
#endif

#if UseNHibernate
services.AddNHibernatePersistenceModel(configuration);
#endif

#if UseMongoDb
services.AddMongoDbPersistence(configuration);
#endif

Benefits:

  • Only includes features that are enabled
  • Reduces binary size
  • Compile-time feature selection
  • Clear feature dependencies

Middleware Pipeline Configuration

Middleware Order Matters

The order of middleware in the pipeline is critical:

public static IApplicationBuilder UseApplicationServices(...)
{
    // 1. Error handling FIRST (catches all exceptions)
    application.UseApplicationProblemDetails();

    // 2. Infrastructure (reverse proxy, logging)
    application.UseForwardedHeaders();
    application.UseApplicationHttpLogging(configuration);

    // 3. Security (HTTPS, CORS)
    application.UseHttpsRedirection();
    application.UseCors();

    // 4. Routing (must be before endpoint-specific middleware)
    application.UseRouting();

    // 5. Feature-specific middleware
    application.UseApplicationRateLimiter();
    application.UseApplicationLocalization();
    application.UseApplicationHeaderPropagation();

    // 6. Endpoints (controllers, health checks, etc.)
    application.UseEndpoints(endpoints =>
    {
        endpoints.MapEndpoints();
    });

    return application;
}

Standard Middleware Pipeline

1. ProblemDetails (Error Responses)
2. ForwardedHeaders (Reverse Proxy)
3. Logging (Request/Response Logging)
4. Exception Handling (Developer/Production Pages)
5. HSTS (HTTPS Enforcement)
6. Azure App Configuration (Dynamic Config Refresh)
7. Conversation ID (Correlation)
8. HTTP Logging (Request/Response Logging)
9. Latency Telemetry (Performance Monitoring)
10. Static Files (wwwroot)
11. WebSockets (Real-time Communication)
12. Service Models (REST/gRPC/CoreWCF)
13. CORS (Cross-Origin Resource Sharing)
14. HTTPS Redirection (Security)
15. Routing (Endpoint Routing)
16. Rate Limiting (Throttling)
17. Localization (Multi-language)
18. Header Propagation (Distributed Tracing)
19. Swagger (API Documentation)
20. Hangfire (Background Jobs Dashboard)
21. OpenTelemetry (Observability)
22. Orleans Dashboard (Actor Model)
23. Endpoints (Controllers, Health Checks, etc.)

Environment-Specific Middleware

if (environment.IsDevelopment())
{
    application.UseDeveloperExceptionPage();
}
else
{
    application.UseHsts();
}

Development:

  • Developer exception page (detailed errors)
  • Enhanced diagnostics

Production:

  • HSTS (HTTP Strict Transport Security)
  • Generic error pages
  • Security headers

Extension Methods Organization

Service Registration Extensions (Add*)

All service registration methods follow the pattern: AddApplication{Feature} or Add{Feature}

// Examples
services.AddApplicationOptions(configuration);
services.AddApplicationHostedServices();
services.AddDomainModelServices();
services.AddPersistenceModel();
services.AddApplicationMassTransit();
services.AddApplicationHealthChecks(configuration);
services.AddApplicationRateLimiting();
services.AddApplicationOpenTelemetry(environment);

Naming Convention:

  • AddApplication{Feature} - Application-specific features
  • Add{Feature} - Generic features (if not application-specific)

Middleware Extensions (Use*)

All middleware configuration methods follow the pattern: UseApplication{Feature} or standard ASP.NET Core names

// Examples
application.UseApplicationProblemDetails();
application.UseApplicationHttpLogging(configuration);
application.UseApplicationRateLimiter();
application.UseApplicationLocalization();
application.UseApplicationHeaderPropagation();
application.UseApplicationSwagger();
application.UseApplicationOpenTelemetry();

Naming Convention:

  • UseApplication{Feature} - Application-specific middleware
  • Standard ASP.NET Core middleware uses standard names (e.g., UseRouting, UseCors)

Endpoint Mapping Extensions (Map*)

All endpoint mapping methods follow the pattern: MapApplication{Feature} or standard ASP.NET Core names

// Examples
endpoints.MapApplicationControllers();
endpoints.MapApplicationHealthChecks();
endpoints.MapApplicationSwagger();
endpoints.MapGrpcServices();
endpoints.MapSignalR();

Naming Convention:

  • MapApplication{Feature} - Application-specific endpoints
  • Standard ASP.NET Core endpoints use standard names (e.g., MapControllers, MapGrpcServices)

Cross-Cutting Concerns Integration

1. Logging

Registration:

#if Serilog
services.AddAndConfigureSerilogLogging(configuration, environment);
#endif

#if Log4Net
loggerFactory.UseLog4Net();
#endif

#if OpenTelemetry
logging.AddOpenTelemetry(options =>
{
    options.IncludeScopes = true;
    options.ParseStateValues = true;
});
#endif

Middleware:

#if Serilog
application.UseApplicationSerilogRequestLogging();
#endif

application.UseApplicationHttpLogging(configuration);

2. Observability

OpenTelemetry:

#if OpenTelemetry
services.AddApplicationOpenTelemetry(environment);
// ... in middleware pipeline
application.UseApplicationOpenTelemetry();
#endif

Application Insights:

#if UseApplicationInsights
services.AddApplicationAzureApplicationInsights();
#endif

Metrics:

services.AddApplicationMetrics();
services.AddLatencyTelemetryCollection();

3. Health Checks

Registration:

#if HealthCheck
services.AddApplicationHealthChecks(configuration);
#endif

Endpoints:

#if HealthCheck
endpoints.MapApplicationHealthChecks();
#endif

4. Caching

Redis:

#if DistributedCacheRedis
services.AddRedisCaching(configuration);
#endif

In-Memory:

#if DistributedCacheInMemory
services.AddInMemoryCaching();
#endif

5. Rate Limiting

Registration:

services.AddApplicationRateLimiting();

Middleware:

application.UseApplicationRateLimiter();

6. Feature Flags

Registration:

#if FeatureFlags
services.AddApplicationFeatureManagement();
#endif

7. Localization

Registration:

services.AddApplicationLocalization();

Middleware:

application.UseApplicationLocalization();

8. Header Propagation

Registration:

services.AddApplicationHeaderPropagation();

Middleware:

application.UseApplicationHeaderPropagation();

Service Model Integration

REST API

Registration:

#if UseRestApi
services.AddApplicationProblemDetails();
services.AddWebApiCommunication();
#endif

Middleware:

#if UseRestApi
application.UseApplicationProblemDetails();
application.UseWebApiCommunication();
#endif

Endpoints:

#if UseRestApi
endpoints.MapApplicationControllers();
#endif

gRPC

Registration:

#if UseGrpc
services.AddGrpcCommunication();
#endif

Endpoints:

#if UseGrpc
endpoints.MapGrpcServices();
#endif

SignalR

Registration:

#if UseSignalR
services.AddSignalRCommunication();
#endif

Endpoints:

#if UseSignalR
endpoints.MapSignalR();
#endif

GraphQL

Registration:

#if UseGraphQL
services.AddGraphQLCommunication();
#endif

Endpoints:

#if UseGraphQL
endpoints.MapGraphQL();
#endif

CoreWCF

Registration:

#if UseCoreWCF
services.AddWcfCoreServiceModel();
#endif

Middleware:

#if UseCoreWCF
application.UseWcfCoreServiceModel();
#endif

Messaging Integration

MassTransit

Registration:

#if UseMassTransit
services.AddApplicationMassTransit();
#endif

Features:

  • Consumers registration
  • Saga state machines
  • Outbox pattern
  • Retry policies

NServiceBus

Registration:

#if UseNServiceBus
services.AddNServiceBus(configuration);
#endif

Startup:

#if UseNServiceBus
application.ApplicationServices.StartNServiceBusEndpoint();
#endif

Shutdown:

#if UseNServiceBus
NServiceBusExtensions.StopNServiceBusEndpoint();
#endif

Persistence Integration

NHibernate

Registration:

#if UseNHibernate
services.AddNHibernatePersistenceModel(configuration);
#endif

#if (UseNHibernate && Migrations)
services.AddApplicationFluentMigrator(configuration, typeof(MicroserviceMigration).Assembly);
#endif

Middleware:

#if UseNHibernate
loggerFactory.UseNHibernateLogging();
application.RunMicroserviceFluentMigrations();
#endif

MongoDB

Registration:

#if UseMongoDb
#if Migrations
services.AddMongoDbMigrator(configuration, typeof(MicroserviceMongoDbMigration));
#endif
services.AddMongoDbPersistence(configuration);
#endif

Background Jobs Integration

Hangfire

Registration:

#if UseHangFire
services.AddApplicationHangFire(configuration);
#endif

Middleware:

#if UseHangFire
application.UseApplicationHangFire();
#endif

Actor Model Integration

Orleans

Host Builder:

#if UseOrleans
hostBuilder.UseMicroserviceOrleans();
#endif

Endpoints:

#if UseOrleans
application.MapOrleansDashboard();
#endif

AI Integration

Semantic Kernel

Registration:

#if UseSemanticKernel
services.AddApplicationSemanticKernel();
#endif

Logging:

#if UseSemanticKernel
loggerFactory.UseSemanticKernelLogging();
#endif

Microsoft Bot Framework

Registration:

#if UseMicrosoftBotBuilder
services.AddApplicationMicrosoftBotBuilder();
#endif

Model Context Protocol (MCP)

Registration:

#if UseMCP
services.AddApplicationMCPServer();
#endif

Endpoints:

#if UseMCP
endpoints.MapApplicationMCPServer();
#endif

Startup Warmup Coordination

StartupWarmupGate

Purpose: Provides grace period and coordination for startup readiness.

Registration:

services.AddApplicationHostedServices();
// Internally registers:
// services.AddSingleton(new StartupWarmupGate(TimeSpan.FromSeconds(20)));
// services.AddHostedService(sp => sp.GetRequiredService<StartupWarmupGate>());

Usage in Health Checks:

public class MyHealthCheck : IHealthCheck
{
    private readonly StartupWarmupGate warmupGate;

    public async Task<HealthCheckResult> CheckHealthAsync(...)
    {
        if (!warmupGate.IsReady)
        {
            return HealthCheckResult.Degraded("Application is still warming up");
        }
        // ... perform health checks
    }
}

Mark Ready After Startup:

hostApplicationLifetime.ApplicationStarted.Register(() =>
{
    var gate = application.ApplicationServices.GetRequiredService<StartupWarmupGate>();
    gate.MarkReady();
});

Use Cases:

  • Database migrations completion
  • Messaging bus connection
  • Cache warmup
  • External service health verification
  • Long-running initialization tasks

Best Practices

Do's

  1. Register Options First

    // ✅ GOOD - Options before services
    services.AddApplicationOptions(configuration);
    services.AddApplicationHostedServices(); // Uses options
    

  2. Use Feature-Based Extension Methods

    // ✅ GOOD - Clear feature boundaries
    services.AddApplicationMassTransit();
    services.AddApplicationHealthChecks(configuration);
    

  3. Follow Naming Conventions

    // ✅ GOOD - Consistent naming
    services.AddApplication{Feature}();
    application.UseApplication{Feature}();
    endpoints.MapApplication{Feature}();
    

  4. Use Conditional Compilation for Optional Features

    // ✅ GOOD - Compile-time feature selection
    #if UseMassTransit
    services.AddApplicationMassTransit();
    #endif
    

  5. Validate Input Parameters

    // ✅ GOOD - Fail-fast validation
    public static IServiceCollection AddMicroserviceFeature(this IServiceCollection services)
    {
        ArgumentNullException.ThrowIfNull(services);
        // ... registration
        return services;
    }
    

  6. Return ServiceCollection for Chaining

    // ✅ GOOD - Fluent interface
    public static IServiceCollection AddMicroserviceFeature(this IServiceCollection services)
    {
        // ... registration
        return services; // Enables chaining
    }
    

Don'ts

  1. Don't Register Services Before Options

    // ❌ BAD - Service needs options
    services.AddApplicationHostedServices(); // Fails if options not registered
    services.AddApplicationOptions(configuration);
    

  2. Don't Mix Business Logic with Registration

    // ❌ BAD - Business logic in registration
    services.AddApplicationFeature(services =>
    {
        if (someBusinessCondition) // Business logic
        {
            // Register services
        }
    });
    

  3. Don't Access Services During Registration

    // ❌ BAD - Service provider not available during registration
    services.AddScoped<IMyService>(sp =>
    {
        var otherService = sp.GetService<IOtherService>(); // May not be registered yet
        return new MyService(otherService);
    });
    

  4. Don't Skip Validation

    // ❌ BAD - No validation
    public static IServiceCollection AddMicroserviceFeature(this IServiceCollection services)
    {
        // No null check - can fail at runtime
        services.AddScoped<IMyService>();
    }
    

  5. Don't Use Wrong Service Lifetime

    // ❌ BAD - Wrong lifetime
    services.AddSingleton<IMyRepository>(); // Repository should be scoped
    
    // ✅ GOOD - Correct lifetime
    services.AddScoped<IMyRepository>();
    

Common Patterns

Pattern 1: Conditional Feature Registration

public static IServiceCollection ConfigureMicroserviceServices(...)
{
    // Always register
    services.AddApplicationOptions(configuration);
    services.AddDomainModelServices();

    // Conditionally register
    #if UseMassTransit
    services.AddApplicationMassTransit();
    #endif

    #if UseNServiceBus
    services.AddNServiceBus(configuration);
    #endif

    return services;
}

Pattern 2: Options-Based Configuration

internal static class MyFeatureExtensions
{
    internal static IServiceCollection AddMicroserviceMyFeature(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // Register options
        services.AddOptions<MyFeatureOptions>()
            .Bind(configuration.GetSection("MyFeature"))
            .ValidateDataAnnotations()
            .ValidateOnStart();

        // Register services using options
        services.AddScoped<IMyFeatureService, MyFeatureService>();

        return services;
    }
}

Pattern 3: Middleware Pipeline Configuration

internal static class MyFeatureExtensions
{
    internal static IApplicationBuilder UseMicroserviceMyFeature(
        this IApplicationBuilder application)
    {
        // Configure middleware
        application.UseMiddleware<MyFeatureMiddleware>();

        return application;
    }
}

Pattern 4: Endpoint Mapping

internal static class MyFeatureExtensions
{
    internal static IEndpointRouteBuilder MapMicroserviceMyFeature(
        this IEndpointRouteBuilder endpoints)
    {
        endpoints.MapGet("/myfeature", async context =>
        {
            await context.Response.WriteAsync("My Feature");
        });

        return endpoints;
    }
}

Pattern 5: Hosted Service Registration

internal static class HostedServicesExtensions
{
    internal static IServiceCollection AddMicroserviceHostedServices(
        this IServiceCollection services)
    {
        services.AddSingleton<StartupWarmupGate>();
        services.AddHostedService(sp => sp.GetRequiredService<StartupWarmupGate>());

        return services;
    }
}

Testing

Unit Testing Extension Methods

[TestMethod]
public void AddMicroserviceFeature_ShouldRegisterServices()
{
    // Arrange
    var services = new ServiceCollection();
    var configuration = new ConfigurationBuilder()
        .AddInMemoryCollection(new Dictionary<string, string>
        {
            ["MyFeature:Enabled"] = "true"
        })
        .Build();

    // Act
    services.AddApplicationFeature(configuration);
    var provider = services.BuildServiceProvider();

    // Assert
    var service = provider.GetService<IMyFeatureService>();
    Assert.IsNotNull(service);
}

Integration Testing

[TestMethod]
public async Task ConfigureMicroserviceServices_ShouldRegisterAllServices()
{
    // Arrange
    var services = new ServiceCollection();
    var configuration = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .Build();
    var environment = new WebHostEnvironment { EnvironmentName = "Development" };

    // Act
    services.ConfigureMicroserviceServices(configuration, environment);
    var provider = services.BuildServiceProvider();

    // Assert
    var domainService = provider.GetService<IDomainService>();
    Assert.IsNotNull(domainService);

    var repository = provider.GetService<IRepository>();
    Assert.IsNotNull(repository);
}

Troubleshooting

Issue: Service Not Registered

Symptoms: InvalidOperationException: Unable to resolve service for type 'X'.

Solutions:

  1. Verify extension method is called in ConfigureMicroserviceServices
  2. Check conditional compilation directives (#if)
  3. Verify service lifetime (Singleton vs Scoped vs Transient)
  4. Check for missing options registration

Issue: Options Not Available

Symptoms: Options are null or default values.

Solutions:

  1. Ensure AddMicroserviceOptions(configuration) is called first
  2. Verify configuration section exists in appsettings.json
  3. Check options validation doesn't fail
  4. Verify options are accessed after service provider is built

Issue: Middleware Order Issues

Symptoms: Middleware doesn't execute or executes in wrong order.

Solutions:

  1. Verify middleware order in UseMicroserviceServices
  2. Check middleware is placed before UseRouting if needed
  3. Ensure endpoint-specific middleware is after UseRouting
  4. Review middleware dependencies

Issue: Startup Warmup Not Working

Symptoms: Health checks fail immediately after startup.

Solutions:

  1. Verify StartupWarmupGate is registered
  2. Check StartupWarmupSeconds configuration
  3. Ensure MarkReady() is called after startup
  4. Verify health checks check IsReady property

References

Summary

The Application Model in ConnectSoft Templates provides:

  • Centralized Orchestration: Single entry point for all service registrations
  • Clean Separation: Application project stays minimal, ApplicationModel handles complexity
  • Consistent Patterns: Standardized extension method naming and organization
  • Feature-Based: Optional features via conditional compilation
  • Middleware Pipeline: Properly ordered middleware configuration
  • Cross-Cutting Concerns: Integrated logging, observability, health checks, etc.
  • Startup Coordination: Warmup gate for graceful startup
  • Testability: Easy to test and override individual components

By following these patterns, teams can:

  • Maintain Clean Architecture: Keep Application project minimal
  • Enable Features Selectively: Use conditional compilation for optional features
  • Ensure Proper Configuration: Options-first registration prevents misconfiguration
  • Build Production-Ready Services: Comprehensive cross-cutting concerns integration
  • Test Effectively: Clear separation enables easy testing
  • Scale Consistently: Standardized patterns across all ConnectSoft templates

The Application Model is the glue that binds all layers and features together, ensuring the application is properly configured, observable, and production-ready.