mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-03 23:43:18 +00:00
237 lines
6.7 KiB
Markdown
237 lines
6.7 KiB
Markdown
# 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`
|