ASP.NET Hosting

How to Use Scrutor to Automate Dependency Injection in.NET: A Comprehensive Guide?

1. Introduction

One of the fundamental ideas of ASP.NET Core and.NET is Dependency Injection (DI). The integrated DI container is:

  • Fast
  • Simple
  • Perfectly adequate for small applications

However, as applications scale—especially when adopting:

  • Clean Architecture
  • Minimal APIs
  • Domain-Driven Design (DDD)

DI configuration frequently becomes repetitious, lengthy, and challenging to manage.

Teams eventually begin to encounter the following problems:

  • Startup files filled with dozens or hundreds of service registrations
  • Cross-cutting concerns such as logging and caching leaking into business logic
  • DI issues becoming harder to debug, particularly with assembly scanning and decorators

This is where Scrutor helps.

Scrutor is a lightweight extension to the default .NET DI container that adds:

  • Assembly scanning
  • Convention-based registration
  • Decorator support

All without having to swap out the integrated container. Even in big systems, Scrutor keeps Dependency Injection clear, scalable, and clean when used properly.

2. What Problem Does Scrutor Solve?

A typical DI setup in a growing application often looks like this:

services.AddScoped<IUserService, UserService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();

This approach introduces several problems:

  • Boilerplate grows rapidly
  • Startup files become unreadable
  • Convention-based architectures are hard to enforce
  • There is no native support for decorators

Scrutor solves these problems by allowing services to be registered by convention instead of repetition.

3. Key Features of Scrutor

Scrutor extends the built-in DI container with a focused set of features:

  • Assembly scanning
    Automatically discovers and registers services from selected assemblies
  • Convention-based registration
    Registers services based on naming conventions or marker interfaces
  • Decorator support
    Adds logging, caching, validation, or metrics without modifying business logic
  • Built-in DI compatibility
    Works seamlessly with Microsoft’s DI container without replacing it

4. How to Install Scrutor

Installing Scrutor requires only a single command:

dotnet add package Scrutor

There is no additional configuration required.

5. When Should You Use Scrutor?

Scrutor is a good fit when:

  • Your application contains many services
  • You follow Clean Architecture or DDD
  • You want decorator support without switching to Autofac
  • You prefer convention over configuration

Scrutor may not be necessary when:

  • The project is very small
  • You want every registration to be explicit
  • You do not need assembly scanning or decorators

6. Scrutor Summary

At a glance, Scrutor:

  • Extends Microsoft’s DI container
  • Reduces repetitive service registration
  • Enables clean decorator patterns
  • Improves scalability
  • Keeps DI configuration readable and predictable

7. Real-World ASP.NET Core Example (Controllers)

Consider the following project structure:

MyApp.Api
MyApp.Application
 ├── Services
 ├── Interfaces
 └── Abstractions
MyApp.Infrastructure
 ├── Repositories
 └── Decorators

A recommended best practice is to use a marker interface to define application services:

public interface IApplicationService { }

A service implementation might look like this:

public class UserService : IUserService, IApplicationService
{
    private readonly IUserRepository _repository;

    public UserService(IUserRepository repository)
    {
        _repository = repository;
    }

    public Task<UserDto?> GetUserAsync(int id)
        => _repository.GetByIdAsync(id);
}

Using Scrutor, service registration becomes concise and expressive:

builder.Services.Scan(scan => scan
    .FromAssemblies(
        typeof(IApplicationService).Assembly,
        typeof(IUserRepository).Assembly
    )
    .AddClasses(c => c.AssignableTo<IApplicationService>())
    .AsImplementedInterfaces()
    .WithScopedLifetime()

    .AddClasses(c => c.Where(t => t.Name.EndsWith("Repository")))
    .AsImplementedInterfaces()
    .WithScopedLifetime()
);

Controllers consume services normally:

[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
    private readonly IUserService _service;

    public UsersController(IUserService service)
    {
        _service = service;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
        => Ok(await _service.GetUserAsync(id));
}

8. Best Practices

Follow these best practices when using Scrutor:

  • Use Scrutor for groups of services, not for everything
  • Use marker interfaces to define service boundaries
  • Be explicit and consistent with lifetimes
  • Keep decorators limited to technical cross-cutting concerns
  • Enable DI validation in development environments
builder.Host.UseDefaultServiceProvider(options =>
{
    options.ValidateScopes = true;
    options.ValidateOnBuild = true;
});

9. Common Pitfalls

Even with Scrutor, Dependency Injection in .NET can go wrong if not used carefully. Some common pitfalls include:

  1. Over-scanning assemblies – registering unintended types or framework classes
  2. Accidental multiple interface registrations – which can cause runtime ambiguity
  3. Lifetime mismatches – for example, singleton services depending on scoped services
  4. Hidden decorator chains – making the execution order unclear or unexpected
  5. Mixing manual and scanned registrations inconsistently – leading to confusion and bugs

Always validate your DI setup during development using ValidateScopes and ValidateOnBuild to catch configuration issues early.

10. When Scrutor Shines

Scrutor is especially effective in:

  • Medium to large APIs
  • Clean Architecture–based systems
  • Microservices
  • Teams enforcing strict conventions
  • Applications with multiple cross-cutting concerns

11. Debugging DI Registrations (ASP.NET Core + Scrutor)

To inspect registered services during startup:

foreach (var service in builder.Services)
{
    Console.WriteLine(
        $"{service.ServiceType}{service.ImplementationType} ({service.Lifetime})");
}

Always validate DI configuration early:

builder.Host.UseDefaultServiceProvider(options =>
{
    options.ValidateScopes = true;
    options.ValidateOnBuild = true;
});

Be careful with decorator order:

services.Decorate<IUserService, CachingUserService>();
services.Decorate<IUserService, LoggingUserService>();

Execution order will be:

Logging → Caching → Core Service

12. Minimal API + Scrutor

Scrutor works exactly the same with Minimal APIs:

Real-World Example

app.MapGet("/users/{id:int}", async (
    int id,
    IUserService service) =>
{
    var user = await service.GetUserAsync(id);
    return user is null ? Results.NotFound() : Results.Ok(user);
});

Controllers and Minimal APIs share the same Dependency Injection pipeline.

13. Common Minimal API Pitfalls

When using Scrutor with Minimal APIs, developers often run into subtle issues that can break DI or lead to unexpected behavior. Common pitfalls include:

  • Resolving services manually instead of using parameter injection in your endpoint delegates
  • Forgetting to align service lifetimes, which can cause scoped services to behave incorrectly
  • Injecting scoped services into singletons, leading to runtime errors or unexpected data sharing
  • Overusing IServiceProvider, which can turn your code into a Service Locator pattern

Always prefer parameter injection in Minimal API endpoints over manual resolution from the service provider. It keeps your code cleaner, safer, and easier to test.

14. DI Anti-Patterns

Avoid these common DI anti-patterns:

  • Service Locator usage
    provider.GetRequiredService<IUserService>();
  • Injecting IServiceProvider everywhere
  • Captive dependencies such as singleton services depending on scoped services
  • Fat constructors with too many dependencies
  • Overusing singleton services

Dependency Injection exposes poor design; it does not fix it.

15. Key Takeaways

  • Scrutor simplifies Dependency Injection without replacing the container
  • Assembly scanning significantly reduces boilerplate
  • Decorators enable clean cross-cutting concerns
  • Minimal APIs and Controllers share the same DI system
  • Debugging and validation are essential
  • Scrutor should be used deliberately, not everywhere

When used wisely, Scrutor makes Dependency Injection in .NET scalable, readable, and maintainable—even in large, production-grade applications. Happy coding.

ASP.NET Core 10.0 Hosting Recommendation

HostForLIFE.eu
HostForLIFE.eu is a popular recommendation that offers various hosting choices. Starting from shared hosting to dedicated servers, you will find options fit for beginners and popular websites. It offers various hosting choices if you want to scale up. Also, you get flexible billing plans where you can choose to purchase a subscription even for one or six months.