Dependency Injection - PogovorovDaniil/Requestum GitHub Wiki

Dependency Injection

Requestum integrates seamlessly with Microsoft.Extensions.DependencyInjection. This guide covers all aspects of service registration and dependency injection patterns.

🔧 Basic Registration

Default Registration

The simplest way to register Requestum:

using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRequestum(cfg =>
{
    // Scans assembly for both handlers and middlewares
    cfg.Default(typeof(Program).Assembly);
});

This is equivalent to:

builder.Services.AddRequestum(cfg =>
{
    cfg.RegisterHandlers(typeof(Program).Assembly);
    cfg.RegisterMiddlewares(typeof(Program).Assembly);
});

Handlers Only

Register only handlers (no middleware):

builder.Services.AddRequestum(cfg =>
{
    cfg.RegisterHandlers(typeof(Program).Assembly);
});

Middlewares Only

Register only middlewares (handlers registered manually):

builder.Services.AddRequestum(cfg =>
{
    cfg.RegisterMiddlewares(typeof(Program).Assembly);
});

📦 Assembly Scanning

Single Assembly

builder.Services.AddRequestum(cfg =>
{
    cfg.RegisterHandlers(typeof(Program).Assembly);
});

Multiple Assemblies

builder.Services.AddRequestum(cfg =>
{
    cfg.RegisterHandlers(
        typeof(Program).Assembly,           // Current assembly
        typeof(UserModule).Assembly,        // User module
        typeof(OrderModule).Assembly,       // Order module
        typeof(PaymentModule).Assembly      // Payment module
    );
});

All Assemblies in Solution

builder.Services.AddRequestum(cfg =>
{
    var assemblies = AppDomain.CurrentDomain.GetAssemblies()
        .Where(a => a.FullName?.StartsWith("YourCompany") == true)
        .ToArray();
    
    cfg.RegisterHandlers(assemblies);
});

⏱️ Service Lifetimes

Global Lifetime

Set default lifetime for all handlers and middlewares:

builder.Services.AddRequestum(cfg =>
{
    // All handlers and middlewares will be Scoped by default
    cfg.Lifetime = ServiceLifetime.Scoped;
    
    cfg.RegisterHandlers(typeof(Program).Assembly);
});

Available Lifetimes:

  • ServiceLifetime.Transient - New instance per request (default)
  • ServiceLifetime.Scoped - One instance per scope (request)
  • ServiceLifetime.Singleton - One instance for application lifetime

Per-Handler Lifetime

Override lifetime for specific handlers:

builder.Services.AddRequestum(cfg =>
{
    // Global default: Transient
    cfg.Lifetime = ServiceLifetime.Scoped;
    
    // Scan assemblies
    cfg.RegisterHandlers(typeof(Program).Assembly);
    
    // Override specific handlers
    cfg.RegisterHandler<CachedUserQueryHandler>(ServiceLifetime.Singleton);
    cfg.RegisterHandler<TransactionalOrderHandler>(ServiceLifetime.Transient);
});

Per-Middleware Lifetime

builder.Services.AddRequestum(cfg =>
{
    cfg.RegisterMiddlewares(typeof(Program).Assembly);
    
    // Override specific middleware
    cfg.RegisterMiddleware(typeof(CachingMiddleware<,>), ServiceLifetime.Singleton);
    cfg.RegisterMiddleware(typeof(TransactionMiddleware<,>), ServiceLifetime.Scoped);
});

🎯 Manual Registration

Register Specific Handler

builder.Services.AddRequestum(cfg =>
{
    // Register specific handlers without assembly scanning
    cfg.RegisterHandler<CreateUserCommandHandler>();
    cfg.RegisterHandler<GetUserQueryHandler>();
    cfg.RegisterHandler<SendWelcomeEmailReceiver>();
});

Register Specific Middleware

builder.Services.AddRequestum(cfg =>
{
    cfg.RegisterMiddleware(typeof(LoggingMiddleware<,>));
    cfg.RegisterMiddleware(typeof(ValidationMiddleware<,>));
    cfg.RegisterMiddleware(typeof(TransactionMiddleware<,>));
});

Mix Scanning and Manual Registration

builder.Services.AddRequestum(cfg =>
{
    // Scan for most handlers
    cfg.RegisterHandlers(typeof(Program).Assembly);
  
    // Manually register from other assemblies
    cfg.RegisterHandler<ExternalApiHandler>(ServiceLifetime.Singleton);
    cfg.RegisterHandler<LegacySystemHandler>(ServiceLifetime.Scoped);
    
    // Manually register middleware
    cfg.RegisterMiddleware(typeof(CustomAuthMiddleware<,>), ServiceLifetime.Scoped);
});

⚙️ Event Handler Configuration

Requiring Event Handlers

By default, events must have at least one receiver:

builder.Services.AddRequestum(cfg =>
{
    cfg.RequireEventHandlers = true; // Default
    cfg.RegisterHandlers(typeof(Program).Assembly);
});

// Publishing event without receivers throws exception
await requestum.PublishAsync(new UserCreatedEvent(...)); // ❌ Exception if no receivers

Optional Event Handlers

Allow publishing events without receivers:

builder.Services.AddRequestum(cfg =>
{
    cfg.RequireEventHandlers = false; // Allow events without receivers
    cfg.RegisterHandlers(typeof(Program).Assembly);
});

// Publishing event without receivers is OK (no-op)
await requestum.PublishAsync(new UserCreatedEvent(...)); // ✅ No error

🔍 Handler Dependencies

Constructor Injection

Handlers receive dependencies via constructor:

public class CreateUserCommandHandler : IAsyncCommandHandler<CreateUserCommand>
{
    private readonly IUserRepository _repository;
    private readonly ILogger<CreateUserCommandHandler> _logger;
    private readonly IEmailService _emailService;
    private readonly IRequestum _requestum;
    
    public CreateUserCommandHandler(
        IUserRepository repository,
        ILogger<CreateUserCommandHandler> logger,
        IEmailService emailService,
        IRequestum requestum)
    {
        _repository = repository;
        _logger = logger;
        _emailService = emailService;
        _requestum = requestum;
    }
    
    public async Task ExecuteAsync(CreateUserCommand command, CancellationToken ct = default)
    {
        // Use injected dependencies
        _logger.LogInformation("Creating user: {Name}", command.Name);
 
        var user = new User(command.Name, command.Email);
        await _repository.AddAsync(user, ct);
 
        await _requestum.PublishAsync(new UserCreatedEvent(user.Id, user.Name, user.Email), ct);
    }
}

Registering Dependencies

var builder = WebApplication.CreateBuilder(args);

// Register your dependencies
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddSingleton<ICacheService, CacheService>();
builder.Services.AddDbContext<AppDbContext>();

// Register Requestum (will use the registered dependencies)
builder.Services.AddRequestum(cfg =>
{
    cfg.Default(typeof(Program).Assembly);
});

List All Registered Handlers

public static class RequestumDiagnostics
{
    public static void PrintRegisteredHandlers(IServiceProvider services)
    {
        var descriptors = services.GetService<IServiceCollection>();
        
        var handlers = descriptors?
            .Where(d => typeof(IBaseHandler).IsAssignableFrom(d.ServiceType))
            .ToList();
        
        Console.WriteLine("Registered Handlers:");
        foreach (var handler in handlers ?? [])
        {
            Console.WriteLine($"- {handler.ServiceType.Name} ({handler.Lifetime})");
        }
    }
}

✅ Best Practices

1. Use Appropriate Lifetimes

// ✅ Good - Transient for stateless handlers
cfg.RegisterHandler<CreateUserCommandHandler>(ServiceLifetime.Transient);

// ✅ Good - Scoped for handlers using DbContext
cfg.RegisterHandler<OrderCommandHandler>(ServiceLifetime.Scoped);

// ✅ Good - Singleton for stateless, cacheable handlers
cfg.RegisterHandler<GetConfigurationQueryHandler>(ServiceLifetime.Singleton);

// ❌ Bad - Singleton with state
public class StatefulHandler : ICommandHandler<MyCommand>
{
    private int _counter; // State! Don't use Singleton
}

2. Avoid Service Locator Anti-Pattern

// ❌ Bad - Service Locator
public class MyHandler : IAsyncCommandHandler<MyCommand>
{
    private readonly IServiceProvider _serviceProvider;
    
    public async Task ExecuteAsync(MyCommand command, CancellationToken ct = default)
    {
        // Don't do this!
        var repository = _serviceProvider.GetRequiredService<IRepository>();
    }
}

// ✅ Good - Constructor Injection
public class MyHandler : IAsyncCommandHandler<MyCommand>
{
    private readonly IRepository _repository;
    
    public MyHandler(IRepository repository)
    {
        _repository = repository;
    }
}

3. Organize Registrations

// ✅ Good - Organized by feature
public static class DependencyInjection
{
    public static IServiceCollection AddApplication(this IServiceCollection services)
    {
        services.AddRequestum(cfg =>
        {
            cfg.Default(typeof(DependencyInjection).Assembly);
        });
        
        return services;
    }
    
    public static IServiceCollection AddInfrastructure(this IServiceCollection services)
    {
        services.AddScoped<IUserRepository, UserRepository>();
        services.AddScoped<IEmailService, EmailService>();
  
        return services;
    }
}

// Usage
builder.Services
    .AddApplication()
    .AddInfrastructure();

← Middleware Pipeline | Home

⚠️ **GitHub.com Fallback** ⚠️