shoc-backend/DI_ASSEMBLY_SCANNING_GUIDE.md

238 lines
6.7 KiB
Markdown
Raw Normal View History

2026-05-14 11:00:12 -05:00
# Dependency Injection - Assembly Scanning Guide
## 🎯 Overview
This project uses **assembly scanning** to automatically register services instead of manual registration. This means you don't need to add each service to `Program.cs` individually.
---
## 🔍 How It Works
### In `Program.cs`
```csharp
// These two lines register ALL services automatically
builder.Services.AddDataServices(); // Scans SeaHaven.DataServices assembly
builder.Services.AddBusinessServices(); // Scans SeaHaven.Services assembly + Validators
```
That's it! No need for this:
```csharp
// ❌ DON'T DO THIS - It's automatic now!
builder.Services.AddScoped<IContactDataService, ContactDataService>();
builder.Services.AddScoped<IEmployeeDataService, EmployeeDataService>();
builder.Services.AddScoped<IAssetDataService, AssetDataService>();
// ... hundreds of lines
```
---
## 📦 DataServicesModule
**Location**: `SeaHaven.DataServices/DependencyInjection/DataServicesModule.cs`
```csharp
public static IServiceCollection AddDataServices(this IServiceCollection services)
{
var assembly = Assembly.GetExecutingAssembly();
// Find all non-abstract classes in the assembly
var allClasses = assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType)
.ToList();
foreach (var implementationType in allClasses)
{
// Find interface matching pattern: I{ClassName}
var defaultInterface = implementationType.GetInterfaces()
.FirstOrDefault(i => i.Name == $"I{implementationType.Name}");
if (defaultInterface != null)
{
// Register as Scoped
services.AddScoped(defaultInterface, implementationType);
}
}
return services;
}
```
### What It Does
1. Scans entire `SeaHaven.DataServices` assembly
2. Finds all concrete classes (e.g., `ContactDataService`, `EmployeeDataService`)
3. For each class, looks for an interface named `I{ClassName}`
4. If found, registers the pair as **Scoped** service
### Example
If you create:
- `IProductDataService` (interface)
- `ProductDataService` (class implementing `IProductDataService`)
Then `IProductDataService → ProductDataService` is **automatically registered** as Scoped.
---
## 💼 ServicesModule
**Location**: `SeaHaven.Services/DependencyInjection/ServicesModule.cs`
```csharp
public static IServiceCollection AddBusinessServices(this IServiceCollection services)
{
var assembly = Assembly.GetExecutingAssembly();
var allClasses = assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType)
.ToList();
foreach (var implementationType in allClasses)
{
var defaultInterface = implementationType.GetInterfaces()
.FirstOrDefault(i => i.Name == $"I{implementationType.Name}");
if (defaultInterface != null)
{
services.AddScoped(defaultInterface, implementationType);
}
}
// ALSO auto-register all FluentValidation validators
services.AddValidatorsFromAssembly(assembly);
return services;
}
```
### What It Does
1. Scans entire `SeaHaven.Services` assembly
2. Registers all services using naming convention (same as DataServices)
3. **BONUS**: Automatically registers ALL FluentValidation validators in the assembly
### Example
If you create:
- `IProductService` (interface)
- `ProductService` (class)
- `CreateProductDTOValidator` (FluentValidation validator)
Then **all three** are automatically registered.
---
## ⚠️ CRITICAL: Naming Convention
For auto-registration to work, you MUST follow this pattern:
### ✅ CORRECT
| Interface | Implementation | Result |
|-----------|---------------|--------|
| `IContactService` | `ContactService` | ✅ Registered as Scoped |
| `IContactDataService` | `ContactDataService` | ✅ Registered as Scoped |
| `IProductService` | `ProductService` | ✅ Registered as Scoped |
| `IEmployeeService` | `EmployeeService` | ✅ Registered as Scoped |
### ❌ WRONG
| Interface | Implementation | Result |
|-----------|---------------|--------|
| `IProductService` | `ProductServiceImpl` | ❌ NOT registered - names don't match |
| `IContactRepository` | `ContactService` | ❌ NOT registered - names don't match |
| `ISomething` | `SomethingElse` | ❌ NOT registered - names don't match |
---
## 📋 Rules
1. **Interface naming**: `I{ClassName}`
2. **Class naming**: `{ClassName}`
3. **Example**:
- Interface: `IContactService`
- Class: `ContactService` (NOT `ContactServiceImpl`, `ContactServiceImplementation`, etc.)
---
## 🚀 Benefits
✅ **Less boilerplate** - No need to add each service to `Program.cs`
✅ **Auto-discovery** - New services are automatically registered
✅ **Consistent** - All services registered the same way
✅ **FluentValidation included** - Validators work automatically
✅ **Cleaner Program.cs** - Only 2 lines instead of 50+
---
## 🎯 How to Add a New Service
### Old Way (Manual)
```csharp
// 1. Create interface and class
public interface IProductService { }
public class ProductService : IProductService { }
// 2. Manually add to Program.cs
builder.Services.AddScoped<IProductService, ProductService>(); // ❌ DON'T DO THIS
```
### New Way (Automatic)
```csharp
// 1. Create interface and class following naming convention
public interface IProductService { }
public class ProductService : IProductService { }
// 2. Done! It's automatically registered ✅
```
---
## 🔧 Lifecycle
All services are registered as **Scoped**:
- New instance per HTTP request
- Shared within the same request
- Disposed at end of request
---
## 📚 Example Flow
1. You create `IProductDataService` and `ProductDataService` in `SeaHaven.DataServices`
2. You create `IProductService` and `ProductService` in `SeaHaven.Services`
3. You create `CreateProductDTOValidator` in `SeaHaven.Services/Validation`
4. Application starts → `AddDataServices()` and `AddBusinessServices()` run
5. Assembly scanner finds your services and validator
6. All registered automatically as Scoped
7. You inject `IProductService` in controller → works immediately ✅
---
## 🐛 Troubleshooting
### Service not being injected?
Check:
1. ✅ Is the interface named `I{ClassName}`?
2. ✅ Is the class named `{ClassName}`?
3. ✅ Is the class in the correct assembly?
4. ✅ Is the class public and concrete (not abstract)?
5. ✅ Does the class implement the interface?
### Validator not working?
Check:
1. ✅ Is the validator in `SeaHaven.Services` assembly?
2. ✅ Does it inherit from `AbstractValidator<T>`?
3. ✅ Is it public and concrete?
---
## 📖 See Also
- **Full Architecture Guide**: `BACKEND_ARCHITECTURE.md`
- **Quick Start Guide**: `QUICK_START_GUIDE.md`
- **At a Glance**: `ARCHITECTURE_AT_A_GLANCE.md`