Scrutor Makes .NET DI Easy - Part II

Continuing from Our Scrutor Journey...

In the previous blog, Scrutor Makes .NET DI Easy Part - I we looked at how Scrutor simplifies service registration in .NET. It showed how to scan assemblies automatically and register services in bulk, making it easier to avoid writing repetitive AddScoped<> lines and keeping the codebase cleaner.

If you haven’t read it yet, I suggest checking it out first. It will help you understand what Scrutor is and how it works.

In this blog, we’ll explore two more powerful features.

  • Registration strategies
  • Decorator pattern

Let’s dive in and see how Scrutor makes all of this much easier and cleaner compared to plain MS.DI.

What Are Registration Strategies?

By default, .NET’s built-in DI adds every service you register. If you do this

1services.AddScoped<IUserService, FirstService>();
2services.AddScoped<IUserService, SecondService>();

It gets added to the service collection. But what if the same interface is already registered?

  • Should we keep the old one?
  • Should we replace it?
  • Or add both?

That decision is called Registration Strategy. Scrutor provides you a method called .UsingRegistrationStrategy(...) that you can use to control what happens when a duplicate is found.

1public enum RegistrationStrategy
2{
3 Append, // Add to the list (default)
4 Skip, // Don’t register if it already exists
5 Replace // Remove the old one and add the new one
6}

❌ Problem with MS.DI

MS.DI doesn't let you control this directly. If you do this:

1services.AddTransient<IUserService, FirstService>();
2services.AddTransient<IUserService, SecondService>();

Then SecondService will replace FirstService silently (for GetService()). You don’t get any warning.

✅ How Scrutor Solves It

Scrutor gives you control using .UsingRegistrationStrategy(...)

1services.Scan(scan => scan
2 .FromAssemblyOf<IUserService>()
3 .AddClasses()
4 .UsingRegistrationStrategy(RegistrationStrategy.Skip)
5 .AsImplementedInterfaces()
6 .WithTransientLifetime()
7);

Examples

  1. Append (Default) Adds everything, last one wins
1services.Scan(scan => scan
2 .FromExecutingAssembly()
3 .AddClasses()
4 .AsImplementedInterfaces()
5 .WithScopedLifetime()
6 .UsingRegistrationStrategy(RegistrationStrategy.Append))
  1. Skip Keeps the first one and ignores duplicates:
1services.Scan(scan => scan
2.FromExecutingAssembly()
3.AddClasses()
4.AsImplementedInterfaces()
5.WithScopedLifetime()
6.UsingRegistrationStrategy(RegistrationStrategy.Skip))
  1. Replace Overrides any earlier registration
1services.Scan(scan => scan
2.FromExecutingAssembly()
3.AddClasses()
4.AsImplementedInterfaces()
5.WithScopedLifetime()
6.UsingRegistrationStrategy(RegistrationStrategy.Replace));

💰Bonus: You can also specify what kind of replacement

  • ServiceType: Replace any existing registration for the interface
  • ImplementationType: Only replace if both interface and implementation match

Now you can say:

  • Skip: Don’t register if already exists.
  • Replace: Replace old one.
  • Append: Keep both (useful for IEnumerable).

Simple and safe!

What is the Decorator Pattern?

The Decorator Pattern means wrapping one service inside another to add extra behavior. Let’s say you have:

1public interface IReportService
2{
3 void Generate();
4}
5
6public class ReportService : IReportService
7{
8 public void Generate() => Console.WriteLine("Report created");
9}

Now you want to add logging before and after, without changing ReportService. You create:

1public class LoggingReportServiceDecorator : IReportService
2{
3 private readonly IReportService _inner;
4
5 public LoggingReportServiceDecorator(IReportService inner)
6 {
7 _inner = inner;
8 }
9
10 public void Generate()
11 {
12 Console.WriteLine("Logging: Start");
13 _inner.Generate();
14 Console.WriteLine("Logging: End");
15 }
16}

❌ MS.DI: No Decorator Support

MS.DI does not support decorators directly. So you need to build it manually:

1services.AddScoped<ReportService>();
2
3services.AddScoped<IReportService>(sp =>
4{
5 var report = sp.GetRequiredService<ReportService>();
6 return new LoggingReportServiceDecorator(report);
7});

It is a bit messy and difficult to manage..

✅ Scrutor: Super Easy Decoration

With Scrutor, it’s just:

1services.AddScoped<IReportService, ReportService>();
2services.Decorate<IReportService, LoggingReportServiceDecorator>();

Even for multiple decorators, it handles the order:

1services.Decorate<IReportService, LoggingReportServiceDecorator>();
2services.Decorate<IReportService, CachingReportServiceDecorator>();

This wraps like:

1Caching(Logging(ReportService))

🧠 Final Thoughts

MS.DI is great for simple cases. But if you:

  • Have a modular project
  • Use plugins or microservices
  • Want better control over service registrations
  • Want to add decorators without the mess

…then Scrutor is your friend.

Whether you're building a big app or a small one, Scrutor makes your dependency injection cleaner and smarter.