📦 Simplifying Dependency Injection in .NET Using Scrutor
Stop Repeating Yourself: Automate DI Registrations with Scrutor
Dependency Injection (DI) is a fundamental design pattern in .NET, enabling clean separation of concerns and better testability. While .NET’s built-in DI container is powerful and easy to use, it can become verbose and repetitive when registering multiple services manually.
Enter Scrutor — a lightweight, open-source library that extends the Microsoft DI container with assembly scanning and decoration support.
In this article, we’ll explore how Scrutor simplifies dependency registration and enhances maintainability.
🔧 Why Scrutor?
Scrutor builds on top of Microsoft.Extensions.DependencyInjection
, offering:
Assembly scanning: Automatically register services by scanning assemblies.
Convention-based registration: Register services based on naming or interface matching.
Decorator support: Apply the decorator pattern without extra boilerplate.
🔧 Problem
When building a .NET application with many services (e.g., OrderService
, CustomerService
, InvoiceService
), manually registering each one becomes repetitive and error-prone:
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<ICustomerService, CustomerService>();
builder.Services.AddScoped<IInvoiceService, InvoiceService>();
// ... and so on
This boilerplate adds up quickly, especially in large-scale projects.
✅ Solution: Assembly Scanning with Scrutor
Scrutor is a powerful library that extends the built-in dependency injection system in .NET. It allows you to automatically register services using convention-based scanning.
The method .FromAssemblyOf<T>()
makes it super easy to scan an entire assembly and register all services at once.
🚀 Getting Started
Step 1: Install Scrutor
Use NuGet or the .NET CLI:
dotnet add package Scrutor
Step 2: Define Interfaces and Implementations
public interface IOrderService
{
void ProcessOrder();
}
public class OrderService : IOrderService
{
public void ProcessOrder()
{
Console.WriteLine("Order processed.");
}
}
🔁 Traditional Way (Manual Registration)
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<ICustomerService, CustomerService>();
// etc...
✅ Modern Way (Using Scrutor)
builder.Services.Scan(scan => scan
.FromAssemblyOf<OrderService>() // Pick any class from your services assembly
.AddClasses() // Add all public non-abstract classes
.AsMatchingInterface() // Match IOrderService -> OrderService
.WithScopedLifetime()); // Set service lifetime (Scoped here)
🔍 What Do These Methods Do?
.FromAssemblyOf<T>()
: Scans the assembly where typeT
(e.g.,OrderService
) is defined..AddClasses()
: Finds all public, non-abstract classes..AsMatchingInterface()
: Automatically mapsOrderService
toIOrderService
if they follow naming conventions..WithScopedLifetime()
: Sets the lifetime. Other options include:.WithSingletonLifetime()
.WithTransientLifetime()
🧠 Example Controller Usage
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpGet("process")]
public IActionResult ProcessOrder()
{
_orderService.ProcessOrder();
return Ok("Order processed.");
}
}
🎯 Benefits
✅ No repetition: Avoid writing dozens of
.AddScoped<TInterface, TImplementation>()
lines.✅ Auto-discovery: Automatically picks up new services added to your assembly.
✅ Cleaner Startup: Keeps your
Program.cs
orStartup.cs
concise and maintainable.✅ Convention-based: Encourages consistent naming and architecture.
📌 Notes
.FromAssemblyOf<T>()
is supported in Scrutor 3.0.0+.Compatible with .NET Core 3.1, .NET 5, .NET 6, .NET 7, and .NET 8.
Make sure your interfaces and implementations follow naming conventions like
IFoo
/Foo
for.AsMatchingInterface()
to work.
💬 Want More?
You can also filter services using:
.AddClasses(classes => classes.Where(type => type.Name.EndsWith("Service")))
Or register by base class:
.AddClasses().AssignableTo<BaseService>()
🧼 Final Thoughts
Using Scrutor
with .FromAssemblyOf<T>()
simplifies service registration and enforces clean, scalable architecture. It’s especially helpful in large projects with many services.
I hope you found this guide helpful and informative.
Thanks for reading!
If you enjoyed this article, feel free to share it and follow me for more practical, developer-friendly content like this.