Skip to content

Commit 7ecf028

Browse files
committed
feat: add support for HostedService in DependencyRegistration
1 parent 23c9c27 commit 7ecf028

31 files changed

+411
-85
lines changed

CLAUDE.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,16 @@ Both generators follow the **Incremental Generator** pattern (IIncrementalGenera
106106
- Generates `AddDependencyRegistrationsFrom{SmartSuffix}()` extension methods with 4 overloads
107107
- **Smart naming** - uses short suffix if unique, full name if conflicts exist
108108
- **Transitive dependency registration** - automatically registers services from referenced assemblies
109+
- **Hosted service detection** - automatically uses `AddHostedService<T>()` for `BackgroundService` or `IHostedService` implementations
109110
- Default lifetime: Singleton (can specify Scoped or Transient)
110111

111112
**Generated Code Pattern:**
112113
```csharp
113114
// Input: [Registration] public class UserService : IUserService { }
114115
// Output: services.AddSingleton<IUserService, UserService>();
116+
117+
// Hosted Service Input: [Registration] public class MaintenanceService : BackgroundService { }
118+
// Hosted Service Output: services.AddHostedService<MaintenanceService>();
115119
```
116120

117121
**Smart Naming:**
@@ -149,6 +153,7 @@ services.AddDependencyRegistrationsFromDomain("DataAccess", "Infrastructure");
149153
- `ATCDIR001` - Service 'As' type must be an interface (Error)
150154
- `ATCDIR002` - Class does not implement specified interface (Error)
151155
- `ATCDIR003` - Duplicate registration with different lifetimes (Warning)
156+
- `ATCDIR004` - Hosted services must use Singleton lifetime (Error)
152157

153158
### OptionsBindingGenerator
154159

@@ -374,7 +379,8 @@ The `PetStore.Api` sample demonstrates all four generators working together in a
374379
┌─────────────────────────────────────────────────────────────┐
375380
│ PetStore.Domain │
376381
│ - [Registration] PetService, ValidationService │
377-
│ - [OptionsBinding] PetStoreOptions │
382+
│ - [Registration] PetMaintenanceService (BackgroundService) │
383+
│ - [OptionsBinding] PetStoreOptions, PetMaintenanceOptions │
378384
│ - [MapTo] Pet → PetDto, Pet → PetEntity │
379385
│ - GenerateDocumentationFile=false │
380386
└─────────────────────────────────────────────────────────────┘
@@ -437,7 +443,8 @@ Return PetDto to client
437443

438444
### Key Features Demonstrated
439445

440-
- **Zero boilerplate DI registration**: All services auto-registered
446+
- **Zero boilerplate DI registration**: All services auto-registered, including hosted services
447+
- **Background service support**: `PetMaintenanceService` automatically registered with `AddHostedService<T>()`
441448
- **Type-safe configuration**: Options validated and bound automatically
442449
- **Automatic mapping chains**: Entity ↔ Domain ↔ DTO conversions
443450
- **OpenAPI integration**: Full API documentation with Scalar UI

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ builder.Services.AddDependencyRegistrationsFromDataAccess();
9393
- **🎯 Auto-Detection**: Automatically registers against all implemented interfaces - no more `As = typeof(IService)`
9494
- **🧹 Smart Filtering**: System interfaces (IDisposable, etc.) are excluded automatically
9595
- **🔍 Multi-Interface**: Implementing multiple interfaces? Registers against all of them
96+
- **🏃 Hosted Service Support**: Automatically detects BackgroundService and IHostedService implementations and uses AddHostedService<T>()
9697
- **✨ Smart Naming**: Generates clean method names using suffixes when unique, full names when conflicts exist
9798
- **⚡ Zero Runtime Cost**: All code generated at compile time
9899
- **🚀 Native AOT Compatible**: No reflection or runtime code generation - fully trimming-safe
@@ -139,6 +140,7 @@ Get errors at compile time, not runtime:
139140
| ATCDIR001 | `As` parameter must be an interface type |
140141
| ATCDIR002 | Class must implement the specified interface |
141142
| ATCDIR003 | Duplicate registration with different lifetimes |
143+
| ATCDIR004 | Hosted services must use Singleton lifetime |
142144

143145
---
144146

docs/generators/DependencyRegistration.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Automatically register services in the dependency injection container using attr
4242
- [❌ ATCDIR001: As Type Must Be Interface](#-ATCDIR001-as-type-must-be-interface)
4343
- [❌ ATCDIR002: Class Does Not Implement Interface](#-ATCDIR002-class-does-not-implement-interface)
4444
- [⚠️ ATCDIR003: Duplicate Registration with Different Lifetime](#️-ATCDIR003-duplicate-registration-with-different-lifetime)
45+
- [❌ ATCDIR004: Hosted Services Must Use Singleton Lifetime](#-ATCDIR004-hosted-services-must-use-singleton-lifetime)
4546
- [📚 Additional Examples](#-additional-examples)
4647

4748
---
@@ -605,13 +606,14 @@ builder.Services.AddDependencyRegistrationsFromApi();
605606
## ✨ Features
606607

607608
- **Automatic Service Registration**: Decorate classes with `[Registration]` attribute for automatic DI registration
609+
- **Hosted Service Support**: Automatically detects `BackgroundService` and `IHostedService` implementations and uses `AddHostedService<T>()`
608610
- **Interface Auto-Detection**: Automatically registers against all implemented interfaces (no `As` parameter needed!)
609611
- **Smart Filtering**: System interfaces (IDisposable, etc.) are automatically excluded
610612
- **Multiple Interface Support**: Services implementing multiple interfaces are registered against all of them
611613
- **Flexible Lifetimes**: Support for Singleton, Scoped, and Transient service lifetimes
612614
- **Explicit Override**: Optional `As` parameter to override auto-detection when needed
613615
- **Dual Registration**: Register services as both interface and concrete type with `AsSelf`
614-
- **Compile-time Validation**: Diagnostics for common errors (invalid interface types, missing implementations)
616+
- **Compile-time Validation**: Diagnostics for common errors (invalid interface types, missing implementations, incorrect hosted service lifetimes)
615617
- **Zero Runtime Overhead**: All code is generated at compile time
616618
- **Native AOT Compatible**: No reflection or runtime code generation - fully trimming-safe and AOT-ready
617619
- **Multi-Project Support**: Each project generates its own registration method
@@ -1102,6 +1104,41 @@ public class UserServiceScoped : IUserService { }
11021104

11031105
**Fix:** Ensure consistent lifetimes or use different interfaces.
11041106

1107+
### ❌ ATCDIR004: Hosted Services Must Use Singleton Lifetime
1108+
1109+
**Severity:** Error
1110+
1111+
**Description:** Hosted services (BackgroundService or IHostedService implementations) must use Singleton lifetime.
1112+
1113+
```csharp
1114+
// ❌ Error: Hosted services cannot use Scoped or Transient lifetime
1115+
[Registration(Lifetime.Scoped)]
1116+
public class MyBackgroundService : BackgroundService
1117+
{
1118+
protected override Task ExecuteAsync(CancellationToken stoppingToken)
1119+
{
1120+
return Task.CompletedTask;
1121+
}
1122+
}
1123+
```
1124+
1125+
**Fix:** Use Singleton lifetime (or default [Registration]) for hosted services:
1126+
1127+
```csharp
1128+
// ✅ Correct: Singleton lifetime (explicit)
1129+
[Registration(Lifetime.Singleton)]
1130+
public class MyBackgroundService : BackgroundService { }
1131+
1132+
// ✅ Correct: Default lifetime is Singleton
1133+
[Registration]
1134+
public class MyBackgroundService : BackgroundService { }
1135+
```
1136+
1137+
**Generated Registration:**
1138+
```csharp
1139+
services.AddHostedService<MyBackgroundService>();
1140+
```
1141+
11051142
---
11061143

11071144
## 📚 Additional Examples

docs/samples/PetStoreApi.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ graph TB
3737
subgraph "PetStore.Domain"
3838
PS["PetService - @Registration"]
3939
VS["ValidationService - @Registration"]
40+
BG["PetMaintenanceService - @Registration (BackgroundService)"]
4041
OPT["PetStoreOptions - @OptionsBinding"]
42+
OPT2["PetMaintenanceServiceOptions - @OptionsBinding"]
4143
PET["Pet - @MapTo(PetResponse)"]
4244
PET2["Pet - @MapTo(PetEntity, Bidirectional=true)"]
4345
DOMENUM["PetStatus (Domain)"]
@@ -86,8 +88,10 @@ graph TB
8688
8789
style PS fill:#0969da
8890
style VS fill:#0969da
91+
style BG fill:#0969da
8992
style PR fill:#0969da
9093
style OPT fill:#0969da
94+
style OPT2 fill:#0969da
9195
style DI1 fill:#2ea44f
9296
style DI2 fill:#2ea44f
9397
style DI3 fill:#2ea44f
@@ -566,11 +570,12 @@ app.Run();
566570

567571
### DependencyRegistration Generator
568572

569-
Creates 2 extension methods with transitive registration:
573+
Creates extension methods with transitive registration:
570574

571575
```csharp
572576
// From PetStore.Domain (with includeReferencedAssemblies: true)
573577
services.AddSingleton<IPetService, PetService>();
578+
services.AddHostedService<PetMaintenanceService>(); // ✨ Automatic BackgroundService registration
574579
services.AddSingleton<IPetRepository, PetRepository>(); // From referenced PetStore.DataAccess
575580
```
576581

@@ -584,6 +589,11 @@ services.AddOptions<PetStoreOptions>()
584589
.Bind(configuration.GetSection("PetStore"))
585590
.ValidateDataAnnotations()
586591
.ValidateOnStart();
592+
593+
services.AddOptions<PetMaintenanceServiceOptions>()
594+
.Bind(configuration.GetSection("PetMaintenanceService"))
595+
.ValidateDataAnnotations()
596+
.ValidateOnStart();
587597
```
588598

589599
### Mapping Generator
@@ -644,6 +654,7 @@ public static Pet MapToPet(this PetEntity source)
644654
### 1. **Complete Integration**
645655
All three generators work seamlessly together:
646656
- Services auto-registered via `[Registration]`
657+
- Background services auto-registered with `AddHostedService<T>()` via `[Registration]`
647658
- Configuration auto-bound via `[OptionsBinding]`
648659
- Objects auto-mapped via `[MapTo]` with bidirectional support
649660

sample/.editorconfig

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ dotnet_diagnostic.CA1062.severity = none # In externally visible meth
5454
dotnet_diagnostic.CA1056.severity = none #
5555
dotnet_diagnostic.CA1303.severity = none #
5656
dotnet_diagnostic.SA1615.severity = none # Element return value should be documented
57+
dotnet_diagnostic.CA1848.severity = none # For improved performance
5758
dotnet_diagnostic.CA1819.severity = none # Properties should not return arrays
5859

5960
dotnet_diagnostic.SA1611.severity = none # The documentation for parameter 'pet' is missing
6061

61-
dotnet_diagnostic.S6580.severity = none # Use a format provider when parsing date and time.
62+
dotnet_diagnostic.S6580.severity = none # Use a format provider when parsing date and time.
63+
dotnet_diagnostic.S6667.severity = none # Logging in a catch clause should pass the caught exception as a parameter.
64+
dotnet_diagnostic.S6672.severity = none # Update this logger to use its enclosing type

sample/PetStore.Api.Contract/PetResponse.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,14 @@ public class PetResponse
3939
/// Gets or sets when the pet was added to the system.
4040
/// </summary>
4141
public DateTimeOffset CreatedAt { get; set; }
42+
43+
/// <summary>
44+
/// Gets or sets when the pet was last modified.
45+
/// </summary>
46+
public DateTimeOffset? ModifiedAt { get; set; }
47+
48+
/// <summary>
49+
/// Gets or sets who last modified the pet.
50+
/// </summary>
51+
public string? ModifiedBy { get; set; }
4252
}

sample/PetStore.Api/GlobalUsings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55

66
global using PetStore.Api.Contract;
77
global using PetStore.Domain;
8+
global using PetStore.Domain.BackgroundServices;
89
global using PetStore.Domain.Services;
910
global using Scalar.AspNetCore;

sample/PetStore.Api/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
includeReferencedAssemblies: true);
1212

1313
// ✨ Register configuration options automatically via [OptionsBinding] attribute
14-
// This single call registers options from PetStore.Domain (PetStoreOptions)
14+
// This single call registers options from PetStore.Domain (PetStoreOptions + PetMaintenanceServiceOptions)
1515
builder.Services.AddOptionsFromDomain(
1616
builder.Configuration,
1717
includeReferencedAssemblies: true);

sample/PetStore.Api/appsettings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@
1010
"MaxPetsPerPage": 20,
1111
"StoreName": "Furry Friends Pet Store",
1212
"EnableAutoStatusUpdates": true
13+
},
14+
"PetMaintenanceService": {
15+
"RepeatIntervalInSeconds": 10
1316
}
14-
}
17+
}

sample/PetStore.DataAccess/Entities/PetEntity.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,14 @@ public class PetEntity
3939
/// Gets or sets when the pet was added to the system.
4040
/// </summary>
4141
public DateTimeOffset CreatedAt { get; set; }
42+
43+
/// <summary>
44+
/// Gets or sets when the pet was last modified.
45+
/// </summary>
46+
public DateTimeOffset? ModifiedAt { get; set; }
47+
48+
/// <summary>
49+
/// Gets or sets who last modified the pet.
50+
/// </summary>
51+
public string? ModifiedBy { get; set; }
4252
}

0 commit comments

Comments
 (0)