# Scrutor 7.0.0 — Auto-Discovery & Decorator Registration

> **Scope:** dotnet-vsa
> **Layer:** 0 (always load)
> **Keywords:** scrutor, di, decorators, auto-discovery, assembly-scanning
> **Load When:** dotnet VSA projects

**Verified against:** .NET 10 + Scrutor 7 (`Scan`/`Decorate` API). Last-verified: 2026-05-20.

---

## Overview

Scrutor provides assembly-scanning and decoration extensions for `IServiceCollection`. In VSA, it replaces manual DI registration with convention-based auto-discovery for handlers, validators, and endpoints.

---

## Handler Auto-Discovery

Scan the assembly for all concrete classes implementing `IHandler<TRequest, TResponse>` and register them as scoped services:

```csharp
public static class HandlerRegistrationExtensions
{
    public static IServiceCollection AddHandlersFromAssembly(
        this IServiceCollection services,
        Assembly assembly)
    {
        var handlerTypes = assembly.GetTypes()
            .Where(t =>
                t.IsClass &&
                !t.IsAbstract &&
                !t.ContainsGenericParameters)
            .ToList();

        foreach (var implementation in handlerTypes)
        {
            var handlerInterfaces = implementation
                .GetInterfaces()
                .Where(i =>
                    i.IsGenericType &&
                    i.GetGenericTypeDefinition() == typeof(IHandler<,>));

            foreach (var handlerInterface in handlerInterfaces)
            {
                services.AddScoped(handlerInterface, implementation);
            }
        }

        // Decorators: last registered = outermost in pipeline
        services.Decorate(typeof(IHandler<,>), typeof(ValidationDecorator<,>));
        services.Decorate(typeof(IHandler<,>), typeof(LoggingDecorator<,>));

        return services;
    }
}
```

---

## Decorator Order

Scrutor applies decorators as a **stack** — last registered wraps outermost:

```
services.Decorate(typeof(IHandler<,>), typeof(ValidationDecorator<,>));  // inner
services.Decorate(typeof(IHandler<,>), typeof(LoggingDecorator<,>));     // outer
```

**Execution order:** `LoggingDecorator` -> `ValidationDecorator` -> `Handler`

This means logging captures validation failures too. Always register validation before logging.

---

## Endpoint Auto-Discovery

Register all `IApiEndpoint` implementations for minimal API mapping:

```csharp
public static IServiceCollection RegisterApiEndpointsFromAssembly(
    this IServiceCollection services, Assembly assembly)
{
    var endpointTypes = assembly.GetTypes()
        .Where(t => t.IsAssignableTo(typeof(IApiEndpoint))
            && t is { IsClass: true, IsAbstract: false, IsInterface: false });

    var serviceDescriptors = endpointTypes
        .Select(type => ServiceDescriptor.Transient(typeof(IApiEndpoint), type))
        .ToArray();

    services.TryAddEnumerable(serviceDescriptors);
    return services;
}

// In Program.cs — map all discovered endpoints
public static WebApplication MapApiEndpoints(this WebApplication app)
{
    var endpoints = app.Services
        .GetRequiredService<IEnumerable<IApiEndpoint>>();

    foreach (var endpoint in endpoints)
    {
        endpoint.MapEndpoint(app);
    }
    return app;
}
```

---

## Validator Auto-Discovery

FluentValidation provides its own assembly scanner. Register it **before** handlers so the `ValidationDecorator` can resolve validators:

```csharp
// Program.cs — registration order matters
builder.Services.AddValidatorsFromAssembly(typeof(CreateBookValidator).Assembly);
builder.Services.AddHandlersFromAssembly(typeof(Program).Assembly);
```

---

## Program.cs Registration Order

The full DI registration sequence in VSA:

```csharp
// 1. Infrastructure (DbContext)
builder.Services.AddSQLDatabaseConfiguration(builder.Configuration);

// 2. Endpoints (auto-discovery)
builder.Services.RegisterApiEndpointsFromAssembly(Assembly.GetExecutingAssembly());

// 3. Validators (must be before handlers)
builder.Services.AddValidatorsFromAssembly(typeof(CreateBookValidator).Assembly);

// 4. Handlers + Decorators (Scrutor)
builder.Services.AddHandlersFromAssembly(typeof(Program).Assembly);

// 5. Cross-cutting
builder.Services.AddExceptionHandler<CustomExceptionHandler>()
    .AddProblemDetails();
```

---

## Anti-Patterns

| Do NOT | Do Instead |
|--------|------------|
| Register handlers manually one-by-one | Use `AddHandlersFromAssembly()` |
| Use `AddSingleton` for handlers | Use `AddScoped` — handlers access DbContext |
| Register decorators before handlers | Decorators go inside `AddHandlersFromAssembly()` after handler scanning |
| Register validators after handlers | Validators must be registered before `AddHandlersFromAssembly()` |
| Forget `!t.ContainsGenericParameters` filter | Open generics (decorators) must be excluded from handler scan |

---

*MORPH-SPEC by Polymorphism Tech*
