An opinionated base implementation of IHostedService for building robust, production-ready background services in .NET with minimal boilerplate code.
ClearHostedService provides a streamlined foundation for creating background services and hosted applications in .NET. It eliminates repetitive setup code by providing sensible defaults for logging, dependency injection, and application lifecycle management.
| Project | Description | Package |
|---|---|---|
| ClearHostedService | Core library for building background services with Serilog logging and isolated DI | ClearMeasure.HostedService |
| ClearHostedEndpoint | Extension for hosting NServiceBus endpoints with SQL persistence support | ClearMeasure.HostedEndpoint |
| ClearHostedEndpoint.SqlServerTransport | SQL Server transport extensions with ambient transaction support | ClearMeasure.HostedEndpoint.SqlServerTransport |
- β
Opinionated Base Implementation: Built on top of
IHostedServicewith best practices baked in - π Rich Logging: Pre-configured Serilog integration with console, file, and ApplicationInsights support
- π Isolated Dependency Injection: Each hosted service instance has its own
IServiceProvider - π Lifecycle Hooks:
OnStartingAsyncandOnStoppingAsyncfor initialization and cleanup - βοΈ Configurable Options: Control shutdown timeout, error handling, and service naming
- ποΈ Onion Architecture: Clean separation of concerns following architectural best practices
- π Production Ready: Handles startup, shutdown, and error scenarios gracefully
- π§ͺ Testable: Designed with unit testing and integration testing in mind
- π¨ NServiceBus Integration: Seamless hosting of NServiceBus endpoints as background services
- ποΈ SQL Persistence: Built-in SQL Server persistence for sagas and outbox
- π§ Flexible Configuration: Override transport, serialization, persistence, and recoverability settings
- π Endpoint Options: Control concurrency, retries, error queues, and metrics
- π Transport Agnostic: Support for any NServiceBus transport (RabbitMQ, Azure Service Bus, SQL, MSMQ, etc.)
- π― Outbox Support: Enable exactly-once processing with transactional outbox
- β‘ SQL Server Transport Extensions: Ambient transaction support for SQL Server transport via
ClearHostedEndpoint.SqlServerTransport
Create a background service by inheriting from ClearHostedService:
using ClearMeasure.HostedService;
using Microsoft.Extensions.DependencyInjection;
public class MyBackgroundService : ClearHostedService
{
protected override void RegisterDependencyInjection(IServiceCollection services)
{
// Register your application-specific dependencies
services.AddScoped<IMyService, MyService>();
services.AddSingleton<IMyRepository, MyRepository>();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Your main service loop
while (!stoppingToken.IsCancellationRequested)
{
using var scope = ServiceProvider.CreateScope();
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.DoWorkAsync(stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
public override async Task OnStartingAsync(CancellationToken cancellationToken)
{
Logger.Information("Service is initializing...");
// Perform startup validation, warm up caches, etc.
}
public override async Task OnStoppingAsync(CancellationToken cancellationToken)
{
Logger.Information("Service is shutting down...");
// Flush queues, close connections, save state, etc.
}
}Create an NServiceBus endpoint by inheriting from ClearHostedEndpoint:
using ClearMeasure.HostedEndpoint;
using ClearMeasure.HostedEndpoint.Configuration;
using NServiceBus;
public class OrderProcessingEndpoint : ClearHostedEndpoint
{
// Configure endpoint options
protected override EndpointOptions EndpointOptions { get; } = new()
{
EndpointName = "OrderProcessing",
EnableInstallers = true,
MaxConcurrency = 4,
ImmediateRetryCount = 3,
DelayedRetryCount = 3
};
// Configure SQL persistence for sagas
protected override SqlPersistenceOptions SqlPersistenceOptions { get; } = new()
{
ConnectionString = "Server=localhost;Database=OrderDb;Integrated Security=true;",
Schema = "nsb",
EnableSagaPersistence = true
};
// Configure the message transport
protected override void ConfigureTransport(EndpointConfiguration endpointConfiguration)
{
var transport = endpointConfiguration.UseTransport<RabbitMQTransport>();
transport.ConnectionString("host=localhost");
transport.UseConventionalRoutingTopology(QueueType.Quorum);
}
// Register message handlers and services
protected override void RegisterDependencyInjection(IServiceCollection services)
{
services.AddScoped<IOrderService, OrderService>();
services.AddSingleton<IEmailService, EmailService>();
}
}using ClearMeasure.HostedEndpoint;
using ClearMeasure.HostedEndpoint.SqlServerTransport;
using NServiceBus;
public class PaymentEndpoint : ClearHostedEndpoint
{
protected override EndpointOptions EndpointOptions { get; } = new()
{
EndpointName = "PaymentProcessing"
};
protected override void ConfigureTransport(EndpointConfiguration endpointConfiguration)
{
// Use SQL Server transport with ambient transaction support
endpointConfiguration.UseSqlServerTransportWithAmbientTransaction(
connectionString: "Server=localhost;Database=Transport;Integrated Security=true;",
schema: "transport"
);
}
}ClearHostedService is designed around several core principles:
- Convention over Configuration: Sensible defaults that work for most scenarios
- Isolation: Each hosted service manages its own service collection and lifetime
- Observability: Comprehensive logging and monitoring out of the box
- Maintainability: Clean architecture principles for long-term maintainability
- Testability: Designed with unit testing and integration testing in mind
Each ClearHostedService instance has its own isolated IServiceProvider, allowing:
- Multiple instances of the same service type with different configurations
- Independent service lifetimes per hosted service
- No DI container pollution between services
Available lifecycle hooks:
OnStartingAsync(CancellationToken)- Called beforeExecuteAsync, ideal for initializationExecuteAsync(CancellationToken)- Your main service logic (abstract, must implement)OnStoppingAsync(CancellationToken)- Called during graceful shutdown, ideal for cleanup
Graceful shutdown:
- Configurable shutdown timeout (default: 30 seconds)
- Automatic cancellation token propagation
- Proper cleanup and disposal
Built-in Serilog integration with:
- Console logging with customizable output templates
- File logging with rolling intervals
- ApplicationInsights integration (optional)
- Structured logging with enrichment (machine name, user, process ID, thread ID)
Override logging behavior:
protected override void ConfigureLogging()
{
// Fully customize Serilog configuration
}
// OR use simplified options
protected override LoggingOptions GetLoggingOptions()
{
return new LoggingOptions
{
LogLevel = LogEventLevel.Debug,
EnableConsoleLogging = true,
EnableFileLogging = true,
ApplicationInsightsConnectionString = "your-connection-string"
};
}All virtual methods available for override:
RegisterDependencyInjection(IServiceCollection)- Register your servicesCreateServiceCollection()- Provide a custom service collectionBuildServiceProviderAsync(IServiceCollection, CancellationToken)- Custom service provider creationConfigureLogging()- Full Serilog configuration controlGetLoggingOptions()- Simplified logging configurationOnStartingAsync(CancellationToken)- Pre-execution initializationOnStoppingAsync(CancellationToken)- Pre-disposal cleanup
Simplified endpoint hosting:
- Automatic endpoint lifecycle management
- Seamless integration with ClearHostedService logging and DI
- Access to
EndpointInstancefor sending messages - Convention-based endpoint naming
EndpointOptions:
EndpointName- Endpoint name (defaults to class name)EnableInstallers- Auto-create queues/tables (default: true)PurgeOnStartup- Clear queue on startup (dev only, default: false)ErrorQueue- Error queue name (default: "error")AuditQueue- Audit queue name (optional)MaxConcurrency- Message processing concurrency (default: 1)ImmediateRetryCount- Immediate retry attempts (default: 3)DelayedRetryCount- Delayed retry attempts (default: 3)DelayedRetryTimeIncrease- Retry delay increment (default: 10 seconds)EnableMetrics- Enable NServiceBus metrics (default: false)EnableOutbox- Enable transactional outbox (default: false)OutboxCleanupBatchSize- Outbox cleanup size (default: 100)OutboxTimeToKeepDeduplicationData- Deduplication retention (default: 7 days)
SqlPersistenceOptions:
ConnectionString- SQL connection string (required for SQL persistence)Schema- Database schema (default: "dbo")TablePrefix- Table prefix (defaults to endpoint name)EnableSagaPersistence- Enable saga storage (default: true)EnableSubscriptionStorage- Enable subscription storage (default: false)SubscriptionCachePeriod- Cache period (default: 5 seconds)
Supports all NServiceBus transports:
- RabbitMQ
- Azure Service Bus
- Amazon SQS
- SQL Server (with ambient transaction support via extension package)
- MSMQ
- Learning Transport
Abstract method to implement:
protected override void ConfigureTransport(EndpointConfiguration endpointConfiguration)
{
// Configure your chosen transport
}All virtual methods available for override:
ConfigureTransport(EndpointConfiguration)- Configure message transport (required)ConfigureEndpoint(EndpointConfiguration)- Additional synchronous configurationConfigureEndpointAsync(EndpointConfiguration, CancellationToken)- Async configurationConfigureSerialization(EndpointConfiguration)- Message serialization (default: SystemTextJson)ConfigurePersistence(EndpointConfiguration)- Persistence configuration (default: SQL or Learning)ConfigureRecoverability(EndpointConfiguration)- Retry and error handling
ClearHostedEndpoint.SqlServerTransport package provides:
UseSqlServerTransportWithAmbientTransaction()- Configure SQL transport with TransactionScopeStorageContext- Access to the ambient database connection/transactionStorageContextBehavior- Pipeline behavior for connection management
Example with ambient transactions:
public class MyHandler : IHandleMessages<MyMessage>
{
private readonly StorageContext _storageContext;
public MyHandler(StorageContext storageContext)
{
_storageContext = storageContext;
}
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
// Use the same connection/transaction as NServiceBus
using var command = _storageContext.Connection.CreateCommand();
command.Transaction = _storageContext.Transaction;
command.CommandText = "INSERT INTO Orders VALUES (@OrderId)";
await command.ExecuteNonQueryAsync();
}
}- .NET 10.0 SDK or later
- PowerShell 7+ (for build script)
- Bash (for Linux/macOS build script)
The project includes cross-platform build scripts in the src/ directory:
Windows (PowerShell):
# Build with default settings (Release configuration)
.\src\build.ps1
# Build in Debug mode
.\src\build.ps1 -Configuration Debug
# Clean build
.\src\build.ps1 -Clean
# Build and create NuGet packages
.\src\build.ps1 -Pack
# Build without running tests
.\src\build.ps1 -SkipTests
# Custom package output directory
.\src\build.ps1 -Pack -PackageOutputPath "C:\packages"
# Verbose output
.\src\build.ps1 -VerboseOutput
# Combined example: Clean Release build with packages
.\src\build.ps1 -Clean -Pack -Configuration ReleaseWindows (Command Prompt):
src\build.cmd
src\build.cmd -Configuration Debug -CleanLinux/macOS (Bash):
./src/build.sh
./src/build.sh --configuration Debug --clean
./src/build.sh --packThe build script (build.ps1) provides:
- β Prerequisites Check - Verifies .NET SDK installation
- π§Ή Clean - Removes bin/obj directories
- π¦ NuGet Restore - Restores package dependencies
- π¨ Build - Compiles all projects in dependency order
- π§ͺ Test - Runs all unit tests with xUnit
- π¦ Pack - Creates NuGet packages for library projects
- π Summary - Shows build duration and results
Projects built in order:
- ClearMeasure.HostedService
- ClearMeasure.HostedEndpoint
- ClearMeasure.HostedEndpoint.SqlServerTransport
- ClearHostedService.Tests
- ClearHostedEndpoint.Tests
Package output:
- Default location:
src/artifacts/packages/ - Includes
.nupkgfiles - Includes symbol packages (
.snupkg) for Release builds
| Parameter | Description | Default |
|---|---|---|
-Configuration |
Build configuration (Debug/Release) | Release |
-Clean |
Clean before build | false |
-SkipTests |
Skip running tests | false |
-Pack |
Create NuGet packages | false |
-PackageOutputPath |
Package output directory | ./artifacts/packages |
-VerboseOutput |
Enable verbose MSBuild output | false |
You can also build manually using dotnet CLI:
# Restore packages
dotnet restore src/ClearHostedService.sln
# Build solution
dotnet build src/ClearHostedService.sln --configuration Release
# Run tests
dotnet test src/ClearHostedService.sln --configuration Release
# Create packages
dotnet pack src/ClearHostedService/ClearMeasure.HostedService.csproj -c Release -o ./packages
dotnet pack src/ClearHostedEndpoint/ClearMeasure.HostedEndpoint.csproj -c Release -o ./packages
dotnet pack src/ClearHostedEndpoint.SqlServerTransport/ClearMeasure.HostedEndpoint.SqlServerTransport.csproj -c Release -o ./packagesThis library follows Onion Architecture principles with clear separation between:
- Core Domain: Business logic and domain models (Interfaces, Exceptions)
- Application Services: Orchestration and use cases (ClearHostedService, ClearHostedEndpoint)
- Infrastructure: Logging, external dependencies, and cross-cutting concerns (Configuration, Extensions)
- Presentation: Hosted service entry points
For detailed architecture documentation, see ARCHITECTURE.md.
# ClearHostedService - Core library
dotnet add package ClearMeasure.HostedService
# ClearHostedEndpoint - NServiceBus integration
dotnet add package ClearMeasure.HostedEndpoint
# ClearHostedEndpoint.SqlServerTransport - SQL Server transport extensions
dotnet add package ClearMeasure.HostedEndpoint.SqlServerTransportAfter building with -Pack:
# Add local package source
dotnet nuget add source "D:\cm-internal\MultiServiceQuickStart\src\artifacts\packages" --name LocalPackages
# Install packages
dotnet add package ClearMeasure.HostedService --source LocalPackages
dotnet add package ClearMeasure.HostedEndpoint --source LocalPackages
dotnet add package ClearMeasure.HostedEndpoint.SqlServerTransport --source LocalPackagesClearHostedService is ideal for:
- Background workers and data processors
- Scheduled tasks and recurring jobs
- Message queue consumers
- Monitoring and health check services
- Microservices and standalone applications
- Console applications requiring DI and logging
ClearHostedService/
??? Core/ # Domain models and core abstractions
??? Application/ # ClearHostedService implementation
??? Infrastructure/ # Logging, configuration, external services
??? Extensions/ # Helper methods and service registration
ClearHostedEndpoint/
??? Core/ # Endpoint exceptions
??? Application/ # ClearHostedEndpoint implementation
??? Infrastructure/ # EndpointOptions, SqlPersistenceOptions
- .NET 10.0 or later
- Microsoft.Extensions.Hosting
- Microsoft.Extensions.DependencyInjection
- Serilog (for logging)
- ApplicationInsights (optional, for monitoring)
- NServiceBus 9.x (for ClearHostedEndpoint)
- SimpleWorker - Basic background worker
- DataProcessorService - DI and batch processing
- ScheduledTaskService - Time-based scheduling
- NServiceBusEndpoint - NServiceBus message endpoint
- NServiceBusWebApp - Web app sending messages to endpoint
Contributions are welcome! Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
| Task | Command/Code |
|---|---|
| Build the solution | .\src\build.ps1 |
| Clean build | .\src\build.ps1 -Clean |
| Build packages | .\src\build.ps1 -Pack |
| Run tests | .\src\build.ps1 (tests run by default) |
| Skip tests | .\src\build.ps1 -SkipTests |
| Debug build | .\src\build.ps1 -Configuration Debug |
| Create hosted service | Inherit from ClearHostedService |
| Create NServiceBus endpoint | Inherit from ClearHostedEndpoint |
| Access logger | Use Logger property |
| Access DI container | Use ServiceProvider property |
| Send NServiceBus message | Use EndpointInstance.Send() or .Publish() |
| Type | Purpose |
|---|---|
ClearHostedService |
Base class for background services |
ClearHostedEndpoint |
Base class for NServiceBus endpoints |
IHostedServiceLifecycle |
Interface for lifecycle hooks |
HostedServiceOptions |
Configuration for ClearHostedService |
EndpointOptions |
Configuration for ClearHostedEndpoint |
SqlPersistenceOptions |
SQL persistence configuration |
LoggingOptions |
Serilog logging configuration |
StorageContext |
SQL Server transport ambient transaction context |
ClearHostedService:
RegisterDependencyInjection(IServiceCollection)- Register servicesConfigureLogging()- Configure SerilogGetLoggingOptions()- Simple logging configOnStartingAsync(CancellationToken)- Initialization hookOnStoppingAsync(CancellationToken)- Cleanup hookExecuteAsync(CancellationToken)- Main logic (abstract)
ClearHostedEndpoint:
- All ClearHostedService methods, plus:
ConfigureTransport(EndpointConfiguration)- Configure transport (abstract)ConfigureEndpoint(EndpointConfiguration)- Additional configConfigureSerialization(EndpointConfiguration)- Message serializationConfigurePersistence(EndpointConfiguration)- Persistence configConfigureRecoverability(EndpointConfiguration)- Retry config
[Specify License]
For issues, questions, or contributions, please open an issue or submit a pull request.