Blog
Monitoring a .NET app using Scrutor
Scrutor is a NuGet package which extends the ASP.NET built-in dependency injection container with assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection.
The library adds two extension methods to IServiceCollection:
- Scan, which is the entry point to set up your assembly scanning.
- Decorate, which is used to decorate already registered services.
Assembly scanning is not what we are interested in here so I won't detail it, just know that the Scan method allows you to browse some assemblies and to register some implementations of services with the lifetime you want. The Scrutor package has limited functionality but it's very powerful; for example, the assembly scanning is so well designed that you can specify options like the registration strategy you want to apply, which is how you want to handle duplicate services or implementations. Anyway, if you don't know it you should take a moment to discover it (there is a great and very detailed blog post by Andrew Lock about it: Using Scrutor to automatically register your services with the ASP.NET Core DI container).
The method we want to use here is the Decorate method, which allows you to decorate already registered services. You register your decorated service, then you can decorate it with a decorator service, and you can still add decorators of decorators if you need to.
What we want to do here is a basic need: we want to know how much time some methods take to complete.
Assume we have the default .NET 6 Api application.
The first thing we do is to create a service interface and its implementation, to get the forecast logic out of the controller.
We have this:
public interface IWeatherService
{
WeatherForecast[] GetForecast();
}
public class WeatherService : IWeatherService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecast[] GetForecast()
{
Thread.Sleep(Random.Shared.Next(10, 50));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
We clean up the controller to resolve the service and call the method it exposes:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly IWeatherService _weatherService;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IWeatherService weatherService)
{
_logger = logger;
_weatherService = weatherService;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return _weatherService.GetForecast();
}
}
And in the program class we register our service and its implementation, like this:
builder.Services.AddSingleton<IWeatherService, WeatherService>();
How can we now add some monitoring to our app? We could add some measurements in our service but they have nothing to do there, and if we do this we should add a mechanism to allow us to disable it if we want, so it becomes complex and invasive. This is where Scrutor comes in: this package allows us to add a decorator to our WeatherForecastService which will take the responsibility of the measuring stuff and only this, so our service won't have to change, it won't even know that it's being measured!
How to do this? It's pretty simple.
We add another service, let's call it MeasuredWeatherService, which implements the same interface as our WeatherService and takes the IWeatherService as a constructor parameter. Then you implement the interface by taking care of the monitoring logic and calling the decorated service for the core logic:
public class MeasuredWeatherService : IWeatherService
{
private readonly ILogger<MeasuredWeatherService> _logger;
private readonly IWeatherService _weatherService;
public MeasuredWeatherService(ILogger<MeasuredWeatherService> logger, IWeatherService weatherService)
{
_logger = logger;
_weatherService = weatherService;
}
public WeatherForecast[] GetForecast()
{
var sw = Stopwatch.StartNew();
try
{
return _weatherService.GetForecast();
}
finally
{
sw.Stop();
_logger.LogInformation("{MethodName} took {Duration}ms to complete", nameof(GetForecast), sw.ElapsedMilliseconds);
}
}
}
We don't even have to change the controller, the only thing we have to do is to register our decorator:
builder.Services.Decorate<IWeatherService, MeasuredWeatherService>();
At runtime the Dependency Injection container will resolve the decorator service, which references the decorated one, so at execution we will find our measurements in the traces:

You can check the source code on my GitHub: @AdrienTorris