Skip to content

Scrutor

Developer UtilitiesDependency InjectionC#

What it is

Scrutor adds assembly scanning and decoration to the built-in .NET dependency injection container, without replacing it.

Scan assemblies to register types by convention, and wrap existing registrations with decorators — both as extension methods on IServiceCollection.

Installation

dotnet add package Scrutor

Getting started

The smallest useful thing you can do with it, and what each part means.

Convention-based registration
services.Scan(scan => scan
    .FromAssemblyOf<BookService>()
        .AddClasses(c => c.Where(t => t.Name.EndsWith("Service")))
            .AsImplementedInterfaces()
            .WithScopedLifetime()
        .AddClasses(c => c.AssignableTo(typeof(IRepository<>)))
            .AsImplementedInterfaces()
            .WithScopedLifetime());

// Replaces dozens of lines like:
// services.AddScoped<IBookService, BookService>();
// services.AddScoped<IAuthorService, AuthorService>();
Convention registration keeps the composition root short, at the cost of making registrations less greppable — worth it past roughly twenty services.

Advanced usage

Where the library earns its place over a simpler alternative.

Decorators
services.AddScoped<IBookRepository, SqlBookRepository>();

// Each wraps the previous; consumers still inject IBookRepository.
services.Decorate<IBookRepository, CachingBookRepository>();
services.Decorate<IBookRepository, LoggingBookRepository>();

public class CachingBookRepository : IBookRepository
{
    private readonly IBookRepository _inner;   // the wrapped instance
    private readonly IMemoryCache _cache;

    public CachingBookRepository(IBookRepository inner, IMemoryCache cache)
        => (_inner, _cache) = (inner, cache);

    public Task<Book?> GetAsync(int id) =>
        _cache.GetOrCreateAsync($"book:{id}", _ => _inner.GetAsync(id));
}
Caching and logging become separate classes rather than conditionals inside the repository. The order of Decorate calls determines the nesting, outermost last.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Could not find any registered services for type
Decorate ran before the underlying registration. Register the implementation first.
A type is registered twice
Overlapping scan predicates. Narrow the filters, or use AsSelfWithInterfaces deliberately.

Best practices

  • Use Scrutor before reaching for a third-party container — it usually covers the gap.
  • Register the base implementation before decorating it, or Decorate throws.
  • Keep scan predicates narrow; over-broad scanning registers types you did not intend.
  • Prefer decorators over conditional logic inside a service for cross-cutting concerns.

Background

Why it exists, and what it was reacting to.

Microsoft's container is deliberately minimal and omits scanning and decorators. Scrutor adds exactly those two capabilities, which is often the entire reason teams considered switching to Autofac.