Hosted Service Auto-Discovery¶
Needlr automatically discovers and registers classes that inherit from BackgroundService or implement IHostedService. No additional attributes are required.
How It Works¶
When you use [GenerateTypeRegistry], Needlr scans for:
- Classes inheriting from
Microsoft.Extensions.Hosting.BackgroundService - Classes directly implementing
Microsoft.Extensions.Hosting.IHostedService
These are automatically registered with the dual-registration pattern:
// Generated code:
services.AddSingleton<MyWorkerService>();
services.AddSingleton<IHostedService>(sp => sp.GetRequiredService<MyWorkerService>());
This pattern allows:
- Resolving by concrete type (
MyWorkerService) - Resolution via
IHostedServicefor the host to start/stop - Decorators to be applied via
IHostedService
Automatic Startup¶
When using .ForWebApplication() or .ForHost() to build a WebApplication or IHost, your discovered hosted services will automatically start when you call StartAsync() or RunAsync():
var app = new Syringe()
.UsingSourceGen()
.ForWebApplication()
.BuildWebApplication();
// Your hosted services start here
await app.RunAsync();
The .NET host runtime handles starting all IHostedService registrations, including your auto-discovered background services. They will also be stopped when the application shuts down.
Basic Example¶
// Just inherit from BackgroundService - no attributes needed
public sealed class OrderProcessingWorker : BackgroundService
{
private readonly IOrderQueue _orderQueue;
public OrderProcessingWorker(IOrderQueue orderQueue)
{
_orderQueue = orderQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var order = await _orderQueue.DequeueAsync(stoppingToken);
// Process order...
}
}
}
Excluding a Hosted Service¶
Use [DoNotAutoRegister] to prevent auto-discovery:
[DoNotAutoRegister]
public sealed class ManuallyRegisteredWorker : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
=> Task.CompletedTask;
}
Decorating Hosted Services¶
You can apply decorators to all hosted services using [DecoratorFor<IHostedService>]:
[DecoratorFor<IHostedService>(Order = 0)]
public sealed class TimingHostedServiceDecorator : IHostedService
{
private readonly IHostedService _inner;
private readonly ILogger<TimingHostedServiceDecorator> _logger;
public TimingHostedServiceDecorator(
IHostedService inner,
ILogger<TimingHostedServiceDecorator> logger)
{
_inner = inner;
_logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
await _inner.StartAsync(cancellationToken);
_logger.LogInformation("Started {Type} in {Elapsed}ms",
_inner.GetType().Name, sw.ElapsedMilliseconds);
}
public Task StopAsync(CancellationToken cancellationToken)
=> _inner.StopAsync(cancellationToken);
}
Multi-Level Decoration¶
Multiple decorators can be stacked using the Order property:
[DecoratorFor<IHostedService>(Order = 0)] // Innermost (closest to service)
public sealed class TrackerDecorator : IHostedService { ... }
[DecoratorFor<IHostedService>(Order = 1)]
public sealed class LoggingDecorator : IHostedService { ... }
[DecoratorFor<IHostedService>(Order = 2)] // Outermost
public sealed class MetricsDecorator : IHostedService { ... }
Resolution order: MetricsDecorator → LoggingDecorator → TrackerDecorator → ActualService
Resolution Behavior¶
var provider = new Syringe()
.UsingSourceGen()
.BuildServiceProvider();
// Concrete resolution - NOT decorated
var worker = provider.GetRequiredService<OrderProcessingWorker>();
// Interface resolution - decorated
var hostedServices = provider.GetServices<IHostedService>();
// Each IHostedService is wrapped by all applicable decorators
Discovery Rules¶
A type is discovered as a hosted service if:
- ✅ Inherits from
BackgroundServiceOR implementsIHostedService - ✅ Is a concrete class (not abstract)
- ✅ Is accessible (public, or internal in the current assembly)
- ❌ Does NOT have
[DoNotAutoRegister] - ❌ Does NOT have
[DecoratorFor<IHostedService>](decorators are not services)
Notes¶
- Hosted services are always registered as Singleton lifetime
- Decorators with
[DecoratorFor<IHostedService>]are excluded from hosted service discovery to prevent circular dependencies - Works with both source-gen and reflection paths