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
{
[ExcludeFromCodeCoverage]
public static class Program
{
public static async Task<int> Main(string[] args)
{
#if Serilog
Log.Logger = SerilogBootstrapExtensions.CreateBootstrapLogger();
try
{
#endif
ConfigureThreadPool();
IHost host = CreateHostBuilder(args).Build();
await host.RunAsync().ConfigureAwait(false);
return 0;
#if Serilog
}
catch (Exception exception)
{
Log.Fatal(exception, "Application failed to start");
throw;
}
finally
{
await Log.CloseAndFlushAsync().ConfigureAwait(false);
}
#endif
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
#if Serilog
Log.Information("Configuring host builder ...");
#endif
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
.UseSerilogBootstrap()
#endif
;
#if Serilog
Log.Information("Host builder has been configured");
#endif
return hostBuilder;
}
}
}
Serilog Bootstrap Pattern
All templates use SerilogBootstrapExtensions.CreateBootstrapLogger() from ConnectSoft.Extensions.Logging.Serilog and .UseSerilogBootstrap() in the host builder. The bootstrap logger is created early in Main and is automatically upgraded to full configuration when the host starts. This replaces the older .UseSerilog(...) pattern.
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");
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)
.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(configuration);
// 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.AddMicroserviceRedisCaching(configuration);
#endif
#if DistributedCacheInMemory
services.AddMicroserviceInMemoryCaching();
#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.AddMicroserviceGrpcCommunication();
#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
// Microsoft Bot Framework: only in ConnectSoft.MicrosoftBotFrameworkTemplate (forked ApplicationModel)
// services.AddMicroserviceMicrosoftBotBuilder();
#if UseMCP
services.AddMicroserviceMCPServer(configuration);
#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.MapMicroserviceGrpcServices();
#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