shoc-backend/DI_ASSEMBLY_SCANNING_GUIDE.md
2026-05-14 11:00:12 -05:00

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

  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

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)

// 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

  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