![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:
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:
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:
Over-scanning assemblies – registering unintended types or framework classes
Accidental multiple interface registrations – which can cause runtime ambiguity
Lifetime mismatches – for example, singleton services depending on scoped services
Hidden decorator chains – making the execution order unclear or unexpected
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.