6.7 KiB
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
// 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:
// ❌ 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
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
- Scans entire
SeaHaven.DataServicesassembly - Finds all concrete classes (e.g.,
ContactDataService,EmployeeDataService) - For each class, looks for an interface named
I{ClassName} - If found, registers the pair as Scoped service
Example
If you create:
IProductDataService(interface)ProductDataService(class implementingIProductDataService)
Then IProductDataService → ProductDataService is automatically registered as Scoped.
💼 ServicesModule
Location: SeaHaven.Services/DependencyInjection/ServicesModule.cs
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
- Scans entire
SeaHaven.Servicesassembly - Registers all services using naming convention (same as DataServices)
- 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
- Interface naming:
I{ClassName} - Class naming:
{ClassName} - Example:
- Interface:
IContactService - Class:
ContactService(NOTContactServiceImpl,ContactServiceImplementation, etc.)
- Interface:
🚀 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)
// 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)
// 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
- You create
IProductDataServiceandProductDataServiceinSeaHaven.DataServices - You create
IProductServiceandProductServiceinSeaHaven.Services - You create
CreateProductDTOValidatorinSeaHaven.Services/Validation - Application starts →
AddDataServices()andAddBusinessServices()run - Assembly scanner finds your services and validator
- All registered automatically as Scoped
- You inject
IProductServicein controller → works immediately ✅
🐛 Troubleshooting
Service not being injected?
Check:
- ✅ Is the interface named
I{ClassName}? - ✅ Is the class named
{ClassName}? - ✅ Is the class in the correct assembly?
- ✅ Is the class public and concrete (not abstract)?
- ✅ Does the class implement the interface?
Validator not working?
Check:
- ✅ Is the validator in
SeaHaven.Servicesassembly? - ✅ Does it inherit from
AbstractValidator<T>? - ✅ 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