dependency-injection-in-dotnet-with-scrutor

1. Introduction

Dependency Injection (DI) is a foundational concept in .NET and ASP.NET Core. The built-in DI container is:

However, as applications scale—especially when adopting:

DI configuration often becomes verbose, repetitive, and difficult to maintain.

Over time, teams start experiencing the following issues:

This is where Scrutor helps.

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

All without replacing the built-in container. When used correctly, Scrutor keeps Dependency Injection clean, scalable, and explicit—even in large applications.

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:

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:

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:

Scrutor may not be necessary when:

6. Scrutor Summary

At a glance, Scrutor:

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:

  
    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

For a deeper dive into these pitfalls and guidance on avoiding them, check out this detailed guide on common Dependency Injection pitfalls with Scrutor.

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:

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:

To better understand these pitfalls and see real-world examples, learn more about common Dependency Injection pitfalls in .NET Minimal APIs.

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:

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

15. Key Takeaways

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

Happy coding.

I write about modern C#, .NET, and real-world development practices.
Follow me on C# Corner for regular insights, tips, and deep dives.