Hosting in ConnectSoft Templates¶
Purpose & Overview¶
Hosting in ConnectSoft Templates refers to the complete lifecycle management of applications from bootstrap through runtime to graceful shutdown. The templates provide a unified, configurable hosting strategy that supports multiple deployment environments: Docker containers, IIS on Windows, Linux services via systemd, Azure App Services, and Kubernetes.
Hosting encompasses:
- Application Bootstrap: Host builder configuration, dependency injection, and service registration
- Web Server Configuration: Kestrel server setup with HTTP/HTTPS endpoints, protocol support, and limits
- Middleware Pipeline: Request processing pipeline with forwarding, routing, authentication, CORS, and observability
- Environment Configuration: Environment-specific settings via
appsettings.{Environment}.json - Lifecycle Management: Startup, shutdown, and graceful termination hooks
- Multi-Platform Support: Consistent behavior across Docker, IIS, Azure, Linux, and local development
Hosting Philosophy
ConnectSoft treats hosting as a first-class concern with consistent, repeatable patterns across all applications. The template abstracts complexity into composable extension methods while maintaining full control and customization capabilities.
Architecture Overview¶
The hosting architecture follows a layered approach:
Hosting System
├── Entry Point (Program.cs)
│ ├── Host Builder Configuration
│ ├── Logging Setup
│ ├── Configuration Loading
│ └── Thread Pool Configuration
├── Service Registration (MicroserviceRegistrationExtensions)
│ ├── Domain & Application Services
│ ├── Infrastructure (Persistence, Messaging)
│ ├── Observability (Logging, Metrics, Tracing)
│ └── Service Models (REST, gRPC, GraphQL, SignalR)
├── Middleware Pipeline (UseMicroserviceServices)
│ ├── Forwarded Headers
│ ├── Exception Handling
│ ├── Static Files & Default Files
│ ├── Routing & CORS
│ ├── Authentication & Authorization
│ └── Endpoint Mapping
└── Runtime Execution
├── Kestrel Server
├── Health Checks
└── Lifecycle Hooks
Entry Point and Host Builder¶
Program.cs Structure¶
The application entry point uses the traditional host builder pattern:
namespace ConnectSoft.Template.Application
{
public static class Program
{
public static async Task<int> Main(string[] args)
{
// Configure thread pool to prevent starvation
ConfigureThreadPool();
IHost host = CreateHostBuilder(args).Build();
await host.RunAsync().ConfigureAwait(false);
return 0;
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
IHostBuilder hostBuilder =
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => DefineLogging(logging))
.ConfigureAppConfiguration((hostBuilderContext, configurationBuilder) =>
{
DefineConfiguration(args, hostBuilderContext, configurationBuilder);
})
#if UseOrleans
.UseMicroserviceOrleans()
#endif
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = false;
options.ValidateOnBuild = false;
});
webBuilder.CaptureStartupErrors(captureStartupErrors: false);
webBuilder.UseSetting(WebHostDefaults.DetailedErrorsKey, bool.TrueString);
DefineKestrel(webBuilder);
webBuilder.UseStartup<Startup>();
})
#if Serilog
.UseSerilog((hostingContext, services, configuration) =>
configuration
.WriteTo.Console()
.ReadFrom.Configuration(hostingContext.Configuration)
.ReadFrom.Services(services))
#endif
;
return hostBuilder;
}
}
}
Thread Pool Configuration¶
Prevents thread starvation in high-concurrency scenarios:
private static void ConfigureThreadPool()
{
int processorCount = Environment.ProcessorCount;
// Calculate optimal thread pool sizes
// For Orleans workloads, we want more threads than default
int minWorkerThreads = Math.Max(processorCount * 2, 50);
int minIoThreads = Math.Max(processorCount, 25);
// Set minimum worker threads to prevent delays when pool needs to grow
ThreadPool.SetMinThreads(minWorkerThreads, minIoThreads);
}
Configuration Loading¶
Configuration is loaded in priority order:
private static void DefineConfiguration(
string[] args,
HostBuilderContext hostBuilderContext,
IConfigurationBuilder configurationBuilder)
{
configurationBuilder.Sources.Clear();
configurationBuilder
.SetBasePath(hostBuilderContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile(
$"appsettings.{hostBuilderContext.HostingEnvironment.EnvironmentName}.json",
optional: true,
reloadOnChange: true);
#if UseAzureAppConfigurationAsAdditionalConfigurationProvider
IConfigurationRoot configurationRoot = configurationBuilder.Build();
configurationBuilder.AddAzureAppConfiguration(options =>
{
string? azureAppConfigurationConnectionString =
configurationRoot.GetConnectionString("AzureAppConfiguration");
options.Connect(azureAppConfigurationConnectionString)
.Select("ConnectSoft.Template:*", LabelFilter.Null)
.ConfigureRefresh(refresh =>
{
refresh.Register("ConnectSoft.Template:Settings:Sentinel", refreshAll: true);
refresh.SetRefreshInterval(TimeSpan.FromMinutes(30));
});
});
#endif
configurationBuilder
.AddEnvironmentVariables()
.AddCommandLine(args);
}
Startup Class¶
The Startup class separates service registration from middleware configuration:
public class Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
protected IConfiguration Configuration { get; private set; } = configuration;
protected IWebHostEnvironment WebHostEnvironment { get; private set; } = webHostEnvironment;
public virtual void ConfigureServices(IServiceCollection services)
{
services.ConfigureMicroserviceServices(this.Configuration, this.WebHostEnvironment);
}
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment environment,
ILoggerFactory loggerFactory,
IHostApplicationLifetime hostApplicationLifetime)
{
app.UseMicroserviceServices(this.Configuration, environment, loggerFactory, hostApplicationLifetime);
}
}
Kestrel Server Configuration¶
Configuration via appsettings.json¶
Kestrel is configured declaratively through configuration:
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://0.0.0.0:5000"
},
"Https": {
"Url": "https://0.0.0.0:5001",
"Certificate": {
"Path": "/https/aspnetapp.pfx",
"Password": "Password123!"
}
}
},
"Limits": {
"MaxRequestBodySize": 104857600,
"MaxConcurrentConnections": 100,
"MaxConcurrentUpgradedConnections": 50
},
"AllowSynchronousIO": false
}
}
Kestrel Setup in Program.cs¶
private static void DefineKestrel(IWebHostBuilder webBuilder)
{
webBuilder.ConfigureKestrel((context, options) =>
{
// Bind the "Kestrel" section of appsettings.json to Kestrel server options
IConfigurationSection kestrelSection = context.Configuration.GetRequiredSection("Kestrel");
kestrelSection.Bind(options);
// Set HTTP/1.1, HTTP/2, and HTTP/3 protocol support
options.ConfigureEndpointDefaults(listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
});
});
}
Port Binding Considerations¶
| Binding | Purpose | Use Case |
|---|---|---|
0.0.0.0:5000 |
Listen on all interfaces | Docker containers, Kubernetes pods |
localhost:5000 |
Listen on loopback only | Local development, behind reverse proxy |
127.0.0.1:5000 |
Explicit loopback | Same as localhost |
HTTP/2 and gRPC Support¶
HTTP/2 is required for gRPC:
- Kestrel must bind to HTTPS endpoint for browser compatibility
- Protocol support includes HTTP/1.1, HTTP/2, and HTTP/3
- All protocols can be enabled simultaneously
Kestrel Limits¶
| Limit | Purpose | Default |
|---|---|---|
MaxRequestBodySize |
Maximum request body size | 30 MB |
MaxConcurrentConnections |
Maximum concurrent connections | Unlimited |
MaxConcurrentUpgradedConnections |
WebSocket/upgrade connections | Unlimited |
AllowSynchronousIO |
Allow synchronous I/O | false (recommended) |
Service Registration¶
MicroserviceRegistrationExtensions¶
All services are registered via ConfigureMicroserviceServices:
public static IServiceCollection ConfigureMicroserviceServices(
this IServiceCollection services,
IConfiguration configuration,
IWebHostEnvironment environment)
{
// Options and configuration
services.AddMicroserviceOptions(configuration);
// Hosted services
services.AddMicroserviceHostedServices();
// AutoMapper
services.AddAutoMapper();
// Localization
services.AddMicroserviceLocalization();
// Domain model services
services.AddDomainModelServices();
// HTTP context accessor
services.AddHttpContextAccessor();
// Service discovery
services.AddMicroserviceServiceDiscovery();
// HTTP clients
services.AddMicroserviceHttpClient();
// Redaction
services.AddMicroserviceRedaction(configuration, environment);
#if Serilog
services.AddAndConfigureSerilogLogging(configuration, environment);
#endif
// HTTP logging
services.AddMicroserviceHttpLogging(configuration);
#if UseHangFire
services.AddMicroserviceHangFire(configuration);
#endif
#if UseApplicationInsights
services.AddMicroserviceAzureApplicationInsights();
#endif
#if OpenTelemetry
services.AddMicroserviceOpenTelemetry(environment);
#endif
// Metrics
services.AddMicroserviceMetrics();
#if DistributedCacheRedis
services.AddRedisCaching(configuration);
#endif
#if DistributedCacheInMemory
services.AddInMemoryCaching();
#endif
// Routing and rate limiting
services.AddRouting();
services.AddMicroserviceRateLimiting();
#if CORS
services.AddCors();
#endif
#if ResourceMonitoring
services.AddMicroserviceResourceMonitoring();
#endif
// Latency telemetry
services.AddLatencyTelemetryCollection();
#if UseRestApi
services.AddMicroserviceProblemDetails();
services.AddWebApiCommunication();
#endif
#if UseCoreWCF
services.AddWcfCoreServiceModel();
#endif
#if UseMongoDb
#if Migrations
services.AddMongoDbMigrator(configuration, typeof(MicroserviceMongoDbMigration));
#endif
services.AddMongoDbPersistence(configuration);
#endif
#if Migrations
services.AddMicroserviceFluentMigrator(configuration, typeof(MicroserviceMigration).Assembly);
#endif
#if UseNHibernate
services.AddNHibernatePersistenceModel(configuration);
#endif
#if (UseNHibernate || UseMongoDb)
services.AddPersistenceModel();
#endif
#if Swagger && (UseGrpc || UseRestApi || UseAzureFunction)
services.AddSwagger();
#endif
#if UseGrpc
services.AddGrpcCommunication();
#endif
#if UseAzureFunction
services.AddAzureFunctionsCommunication();
#endif
#if HealthCheck
services.AddMicroserviceHealthChecks(configuration);
#endif
// FluentValidation
services.AddFluentValidation();
#if FeatureFlags
services.AddMicroserviceFeatureManagement();
#endif
#if UseAzureAppConfigurationAsAdditionalConfigurationProvider
services.AddMicroserviceAzureAppConfiguration();
#endif
#if UseNServiceBus
services.AddNServiceBus(configuration);
#endif
#if UseMassTransit
services.AddMicroserviceMassTransit();
#endif
// Header propagation
services.AddMicroserviceHeaderPropagation();
#if UseAuditNet
services.AddAuditNet();
#endif
#if UseAzureOpenAI
services.AddMicroserviceAzureOpenAI();
#endif
#if UseOpenAI
services.AddMicroserviceOpenAI();
#endif
#if UseMicrosoftExtensionsAI
services.AddMicrosoftAI();
#endif
#if UseSemanticKernel
services.AddMicroserviceSemanticKernel();
#endif
#if UseMicrosoftBotBuilder
services.AddMicroserviceMicrosoftBotBuilder();
#endif
#if UseMCP
services.AddMicroserviceMCPServer();
#endif
#if UseSignalR
services.AddSignalRCommunication();
#endif
return services;
}
Middleware Pipeline¶
UseMicroserviceServices¶
The middleware pipeline is constructed via UseMicroserviceServices:
public static IApplicationBuilder UseMicroserviceServices(
this IApplicationBuilder application,
IConfiguration configuration,
IWebHostEnvironment environment,
ILoggerFactory loggerFactory,
IHostApplicationLifetime hostApplicationLifetime)
{
// Register shutdown handler
hostApplicationLifetime.ApplicationStopping.Register(OnStopping);
#if UseRestApi
application.UseMicroserviceProblemDetails();
#endif
// Forwarded headers (for reverse proxy support)
application.UseForwardedHeaders();
#if Log4Net
loggerFactory.UseLog4Net();
#endif
#if UseNHibernate
loggerFactory.UseNHibernateLogging();
application.RunMicroserviceFluentMigrations();
#endif
#if UseNServiceBus
loggerFactory.UseNServiceBusLogging();
#endif
#if UseSemanticKernel
loggerFactory.UseSemanticKernelLogging();
#endif
#if Serilog
application.UseMicroserviceSerilogRequestLogging();
#endif
// Exception handling
if (environment.IsDevelopment())
{
application.UseDeveloperExceptionPage();
}
else
{
application.UseHsts();
}
#if UseAzureAppConfigurationAsAdditionalConfigurationProvider
application.UseMicroserviceAzureAppConfiguration();
#endif
// Conversation ID for correlation
application.UseConversationId();
// HTTP logging
application.UseMicroserviceHttpLogging(configuration);
// Latency telemetry
application.UseLatencyTelemetryCollection();
// Static files (serves from wwwroot/)
application.UseDefaultFiles();
application.UseStaticFiles();
// WebSockets
application.UseWebSockets();
#if UseRestApi
application.UseWebApiCommunication();
#endif
#if UseCoreWCF
application.UseWcfCoreServiceModel();
#endif
#if CORS
application.UseCors();
#endif
// HTTPS redirection
application.UseHttpsRedirection();
// Routing
application.UseRouting();
// Rate limiting
application.UseMicroserviceRateLimiter();
// Localization
application.UseMicroserviceLocalization();
// Header propagation
application.UseMicroserviceHeaderPropagation();
#if Swagger && (UseGrpc || UseRestApi || UseAzureFunction)
application.UseMicroserviceSwagger();
#endif
#if UseHangFire
application.UseMicroserviceHangFire();
#endif
#if OpenTelemetry
application.UseMicroserviceOpenTelemetry();
#endif
#if UseOrleans
application.MapOrleansDashboard();
#endif
// Map endpoints
application.UseEndpoints(endpoints =>
{
endpoints.MapEndpoints();
});
#if UseNServiceBus
application.ApplicationServices.StartNServiceBusEndpoint();
#endif
// Mark warmup as complete
hostApplicationLifetime.ApplicationStarted.Register(() =>
{
var gate = application.ApplicationServices.GetRequiredService<StartupWarmupGate>();
gate.MarkReady();
});
return application;
}
Endpoint Mapping¶
Endpoints are mapped via MapEndpoints:
private static void MapEndpoints(this IEndpointRouteBuilder endpoints)
{
#if UseGrpc
endpoints.MapGrpcServices();
#endif
#if HealthCheck
endpoints.MapMicroserviceHealthChecks();
#endif
// Root endpoint
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync(
"Welcome to ConnectSoft.Template's application back-end application",
cancellationToken: context.RequestAborted);
});
#if Swagger && (UseGrpc || UseRestApi || UseAzureFunction)
endpoints.MapMicroserviceSwagger();
#endif
#if Scalar && (UseGrpc || UseRestApi || UseAzureFunction)
endpoints.MapScalar();
#endif
#if UseMCP
endpoints.MapMicroserviceMCPServer();
#endif
#if UseRestApi
endpoints.MapMicroserviceControllers();
#endif
#if UseSignalR
endpoints.MapSignalR();
#endif
}
Middleware Order¶
The middleware pipeline executes in a specific order:
- Forwarded Headers - Process reverse proxy headers first
- Exception Handling - Catch exceptions early
- Logging - Log all requests
- Static Files - Serve static content before routing
- Routing - Determine endpoint
- Authentication - Verify identity
- Authorization - Check permissions
- Endpoints - Execute handler
Forwarded Headers and Reverse Proxy Support¶
UseForwardedHeaders Configuration¶
Required when hosting behind reverse proxies (Nginx, IIS, ingress controllers):
By default, this enables:
- X-Forwarded-For - Original client IP
- X-Forwarded-Proto - Original request scheme (HTTP/HTTPS)
- X-Forwarded-Host - Original host header
Known Proxies¶
For security, configure known proxy IPs:
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownProxies.Add(IPAddress.Parse("172.20.0.1")); // Docker bridge
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)); // Internal network
});
Docker Hosting¶
Dockerfile¶
Multi-stage Dockerfile for production builds:
# Base runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 8081
EXPOSE 7279
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR app
# Copy solution and project files
COPY ["Directory.Packages.props", "."]
COPY ["Directory.Build.props", "."]
COPY ["nuget.config", "."]
COPY ["ConnectSoft.Template.slnx", "."]
# ... copy all project files ...
# Restore dependencies
RUN dotnet restore ./ConnectSoft.Template.Application/ConnectSoft.Template.Application.csproj \
--configfile "nuget.config"
# Copy source code
COPY ["ConnectSoft.Template/.", "./ConnectSoft.Template/"]
# ... copy all source files ...
# Publish application
FROM build AS publish
RUN dotnet publish "/app/ConnectSoft.Template.Application/ConnectSoft.Template.Application.csproj" \
-c Release \
-o /app/publish \
--configfile "/app/nuget.config"
# Final runtime image
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConnectSoft.Template.Application.dll"]
Docker Compose¶
Multi-container orchestration:
services:
connectsoft.applicationtemplate.application:
image: ${DOCKER_REGISTRY-}connectsoft.applicationtemplate.application
container_name: connectsoft.applicationtemplate.application
build:
context: .
dockerfile: ../ConnectSoft.Template.Application/Dockerfile
depends_on:
- sql
- redis
- mongo
- otel-collector
- rabbitmq
networks:
- backend
sql:
image: "mcr.microsoft.com/mssql/server:2022-latest"
container_name: sql
ports:
- "1433:1433"
environment:
- ACCEPT_EULA=y
- MSSQL_SA_PASSWORD=ConnectSoft123!
- MSSQL_MEMORY_LIMIT_MB=4096
networks:
- backend
redis:
image: redis
container_name: redis
ports:
- "60003:60002"
networks:
- backend
mongo:
image: mongo:latest
container_name: mongo
ports:
- "60001:27017"
networks:
- backend
rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
ports:
- "5672:5672"
- "15672:15672"
networks:
- backend
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
container_name: otel-collector
ports:
- "4317:4317"
- "4318:4318"
networks:
- backend
networks:
backend:
driver: bridge
volumes:
httpscerts: {}
HTTPS Certificates in Docker¶
Certificates are mounted as volumes:
services:
certgen:
image: mcr.microsoft.com/dotnet/sdk:8.0
container_name: certgen
entrypoint: ["bash","-lc","dotnet dev-certs https -ep /out/aspnetapp.pfx -p 123456"]
volumes:
- httpscerts:/out
restart: "no"
connectsoft.applicationtemplate.application:
volumes:
- httpscerts:/https:ro
Kestrel configuration references the mounted certificate:
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://0.0.0.0:5001",
"Certificate": {
"Path": "/https/aspnetapp.pfx",
"Password": "123456"
}
}
}
}
}
IIS Hosting¶
web.config¶
For IIS hosting, web.config is required:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet"
arguments=".\ConnectSoft.Template.Application.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
Hosting Models¶
| Model | Configuration | Performance | Use Case |
|---|---|---|---|
| In-Process | hostingModel="inprocess" |
Faster | Recommended for .NET Core 3.1+ |
| Out-of-Process | hostingModel="outofprocess" |
Slightly slower | Matches Docker/Kestrel behavior |
IIS Integration¶
Ensure IIS integration is enabled (auto-detected when running under IIS):
This enables: - Header forwarding - Port resolution from IIS - Graceful shutdown support
Linux Hosting with systemd¶
systemd Service File¶
Create /etc/systemd/system/connectsoft-application.service:
[Unit]
Description=ConnectSoft ConnectSoft Templates
After=network.target
[Service]
Type=notify
WorkingDirectory=/opt/connectsoft/application
ExecStart=/usr/bin/dotnet /opt/connectsoft/application/ConnectSoft.Template.Application.dll
Restart=always
RestartSec=10
SyslogIdentifier=connectsoft-application
User=dotnet
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
Deployment Steps¶
-
Publish the application:
-
Register and start the service:
-
Check status:
-
View logs:
Nginx Reverse Proxy¶
Configure Nginx to proxy to Kestrel:
server {
listen 80;
server_name application.example.com;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Azure App Service Hosting¶
Deployment Options¶
| Method | Tool | Artifact |
|---|---|---|
| ZIP Deploy | Azure CLI, Portal | .zip archive |
| Docker | Container Registry | Docker image |
| GitHub Actions | Azure Deploy Action | Build artifact |
| Azure DevOps | Azure Web App Task | Build artifact |
Configuration¶
Required App Service settings:
| Setting | Value | Purpose |
|---|---|---|
ASPNETCORE_ENVIRONMENT |
Production |
Environment name |
WEBSITES_PORT |
5000 |
Port Kestrel listens on |
DOTNET_RUNNING_IN_CONTAINER |
false |
Container detection |
APPINSIGHTS_INSTRUMENTATIONKEY |
... |
Application Insights |
Connection Strings¶
Configure via App Service Configuration:
ConnectionStrings__DefaultConnection = Server=...;Database=...;...
ConnectionStrings__Redis = redis.redis.cache.windows.net:6380,password=...
HTTPS¶
Azure App Service provides automatic HTTPS certificates via Let's Encrypt. Custom certificates can be bound via the portal or Azure CLI.
Application Lifecycle¶
IHostApplicationLifetime¶
The IHostApplicationLifetime interface provides lifecycle hooks:
public interface IHostApplicationLifetime
{
CancellationToken ApplicationStarted { get; }
CancellationToken ApplicationStopping { get; }
CancellationToken ApplicationStopped { get; }
void StopApplication();
}
Startup Warmup Gate¶
The template uses a warmup gate to prevent health checks from passing before the application is fully initialized:
hostApplicationLifetime.ApplicationStarted.Register(() =>
{
var gate = application.ApplicationServices.GetRequiredService<StartupWarmupGate>();
gate.MarkReady();
});
Graceful Shutdown¶
Shutdown handlers are registered for cleanup:
private static void OnStopping()
{
#if UseNServiceBus
NServiceBusExtensions.StopNServiceBusEndpoint();
#endif
#if Serilog
SerilogLoggingExtensions.CloseAndFlush();
Thread.Sleep(1000); // Allow time for logs to flush
#endif
}
Shutdown Best Practices¶
- Stop accepting new requests: Health checks should fail during shutdown
- Complete in-flight requests: Allow reasonable timeout
- Flush telemetry: Export logs, metrics, and traces
- Close connections: Database, messaging, and external API connections
- Stop background services: Cancellation tokens propagate shutdown signals
Environment Configuration¶
Environment Detection¶
ASP.NET Core uses ASPNETCORE_ENVIRONMENT to determine the environment:
| Value | Use Case | Config File |
|---|---|---|
Development |
Local development | appsettings.Development.json |
Docker |
Containerized environments | appsettings.Docker.json |
Production |
Production deployments | appsettings.Production.json |
Test |
Integration tests | appsettings.Test.json |
Configuration Priority¶
Configuration is merged in order (last wins):
appsettings.json(base)appsettings.{Environment}.json(environment-specific)- Environment variables
- Command-line arguments
- Azure App Configuration (if enabled)
Environment-Specific Behavior¶
if (environment.IsDevelopment())
{
application.UseDeveloperExceptionPage();
}
else
{
application.UseHsts();
}
#if Swagger && (UseGrpc || UseRestApi || UseAzureFunction)
// Swagger typically enabled only in Development
if (environment.IsDevelopment())
{
application.UseMicroserviceSwagger();
}
#endif
Static Files and Default Files¶
Static Files Middleware¶
Serves files from wwwroot/ directory:
application.UseDefaultFiles(); // Serves index.html for root path
application.UseStaticFiles(); // Serves files from wwwroot/
SPA Fallback¶
For single-page applications:
application.UseDefaultFiles();
application.UseStaticFiles();
// Fallback to index.html for client-side routing
application.UseEndpoints(endpoints =>
{
endpoints.MapFallbackToFile("index.html");
});
Static File Options¶
Customize static file serving:
application.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// Add caching headers
ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=3600");
}
});
Security Considerations¶
HTTPS Enforcement¶
Always enforce HTTPS in production:
Configuration:
HSTS (HTTP Strict Transport Security)¶
Enable HSTS for production:
Reverse Proxy Security¶
- Always validate forwarded headers via
KnownProxiesorKnownNetworks - Do not trust headers from untrusted sources
- Use network-level IP restrictions for admin endpoints
Static File Protection¶
Protect sensitive static files:
application.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
if (!ctx.Context.User.Identity.IsAuthenticated)
{
ctx.Context.Response.StatusCode = 403;
ctx.Context.Response.Body = Stream.Null;
}
}
});
Testing and Debugging¶
WebApplicationFactory¶
Integration tests use WebApplicationFactory:
public class MicroserviceTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> factory;
public MicroserviceTests(WebApplicationFactory<Program> factory)
{
this.factory = factory;
}
[TestMethod]
public async Task HealthCheck_ReturnsHealthy()
{
var client = this.factory.CreateClient();
var response = await client.GetAsync("/health");
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
Custom Host Configuration for Tests¶
Override configuration in tests:
var factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((context, config) =>
{
config.AddInMemoryCollection(new Dictionary<string, string>
{
["Kestrel:Endpoints:Http:Url"] = "http://localhost:0"
});
});
});
Logging in Tests¶
Capture logs during test execution:
var factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
});
});
Best Practices¶
Do's¶
- Use Configuration-Driven Setup
- Avoid hardcoded ports, connection strings, or URLs
-
Use
appsettings.{Environment}.jsonfor environment-specific values -
Enable Forwarded Headers
- Always enable when behind reverse proxy
-
Configure known proxies for security
-
Configure Thread Pool
- Set minimum threads to prevent starvation
-
Scale based on expected workload
-
Use Health Checks
- Expose
/health,/alive,/startupendpoints -
Integrate with orchestrator probes
-
Implement Graceful Shutdown
- Flush telemetry on shutdown
- Stop background services cleanly
-
Close connections gracefully
-
Separate Concerns
- Use
Startupclass for clear separation - Extend via extension methods
- Keep
Program.csminimal
Don'ts¶
-
Don't Block Startup
-
Don't Hardcode Ports
-
Don't Trust All Forwarded Headers
-
Don't Skip HTTPS in Production
-
Don't Expose Detailed Errors in Production
Troubleshooting¶
Common Issues¶
| Issue | Symptom | Solution |
|---|---|---|
| Port Already in Use | Service fails to start | Check netstat or lsof, change port in config |
| gRPC Not Working | UNAVAILABLE errors |
Ensure HTTPS and HTTP/2 are enabled |
| Health Checks Fail | 404 on /health |
Verify MapMicroserviceHealthChecks() is called |
| Redirect Loops | Infinite redirects | Check forwarded headers configuration |
| Static Files Not Serving | 404 on static assets | Verify UseStaticFiles() is before UseRouting() |
| Slow Startup | Long initialization time | Profile startup, consider lazy loading |
| Thread Starvation | Slow responses under load | Increase thread pool minimum threads |
Debugging Tips¶
-
Enable Detailed Errors:
-
Check Middleware Order:
-
Use
app.Use(async (context, next) => { ... await next(); })to trace execution -
Validate Configuration:
-
Monitor Startup:
- Add startup logging to track initialization phases
- Use application insights or structured logging
Summary¶
Hosting in the ConnectSoft ConnectSoft Templates provides:
- ✅ Unified Bootstrap: Consistent
Program.csandStartuppattern across all services - ✅ Kestrel Configuration: Declarative server setup via
appsettings.json - ✅ Middleware Pipeline: Composable, ordered request processing pipeline
- ✅ Multi-Platform Support: Docker, IIS, Linux systemd, Azure App Service
- ✅ Reverse Proxy Support: Forwarded headers with security validation
- ✅ Environment Awareness: Environment-specific configuration and behavior
- ✅ Lifecycle Management: Startup warmup gates and graceful shutdown hooks
- ✅ Security: HTTPS enforcement, HSTS, static file protection
- ✅ Observability: Integrated logging, metrics, and tracing from startup
By following these patterns, applications achieve:
- Consistency — All services follow the same hosting patterns
- Flexibility — Supports multiple deployment targets
- Reliability — Graceful startup and shutdown
- Security — Secure-by-default configuration
- Observability — Full lifecycle monitoring and logging
- Maintainability — Clear separation of concerns and testability
The hosting infrastructure ensures that ConnectSoft applications are production-ready, secure, and maintainable across any deployment environment.
Related Documentation¶
- Startup and Warmup: Startup warmup patterns and mechanisms for graceful application initialization
- Kubernetes: Kubernetes deployment patterns
- Containerization: Container deployment strategies
- Docker Compose: Local development with Docker Compose
- Application Model: Application model and service registration