.NET  

How to Automate Dependency Injection in .NET Using Scrutor, Step‑by‑Step Guide

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:

  • Fast

  • Simple

  • Perfectly adequate for small applications

However, as applications scale—especially when adopting:

  • Clean Architecture

  • Minimal APIs

  • Domain-Driven Design (DDD)

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

Over time, teams start experiencing the following issues:

  • 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 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:

  • 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

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:

  • 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

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:

  • 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.

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