Base Transaction Management With Decorator and Mediator Patterns in .NET Core & Scrutor
Hello eveybody! In this article, I will focus on the intricacies of implementing the Decorator design pattern using the Scrutor library in .NET Core. Specifically, I will explore creating a structure where we can manage operations through a single base class for command handler sections when utilizing the CQRS design pattern.
First, I’ll take a brief look at the concepts of Scrutor and the Decorator pattern, and then we’ll proceed to the example I want to highlight.
What is Scrutor?
Scrutor is a library developed for Microsoft.Extensions.DependencyInjection. This library is used to automatically identify and organize dependencies in .NET Core applications. It enables the ability to scan and register classes by traversing the service collection.

Scanning for Services: The Scan method of Scrutor allows us to scan classes within a specific namespace or assembly and add them to the service collection. For example, we can find classes that implement a specific interface and add them to the service collection:
services.Scan(scan => scan .FromAssemblyOf<SomeType>()
.AddClasses(classes => classes.AssignableTo<IScopedService>())
.AsImplementedInterfaces()
.WithScopedLifetime());
Service Registration: We can define classes as those that conform to a specific interface and register them in the DI container:
services.Scan(scan => scan .FromAssemblyOf<SomeType>()
.AddClasses(classes => classes.AssignableTo<IRepository>())
.AsImplementedInterfaces()
.WithTransientLifetime());
Determining Service Lifetime: Scrutor provides flexibility in determining the service lifetime. For instance, we can register classes that conform to a specific interface as singletons:
services.Scan(scan => scan .FromAssemblyOf<SomeType>()
.AddClasses(classes => classes.AssignableTo<ISingletonService>())
.AsImplementedInterfaces()
.WithSingletonLifetime());
Filtering: During the scanner-based registration process, there is also the capability to filter classes that meet specific criteria. For example, we can filter and register classes within a particular namespace:
services.Scan(scan => scan
.FromAssemblyOf<SomeType>()
.AddClasses(classes => classes.InNamespaceOf<SomeType>())
.AsImplementedInterfaces()
.WithTransientLifetime());
What is Decorator Design Pattern?
The Decorator design pattern is a design pattern that allows dynamically extending an object and implementing these extensions without altering the basic functionality of the object. In other words, when you want to add additional functionality to an object, you can provide this functionality through an additional class without modifying the object itself.

The Decorator pattern supports the Open/Closed principle (a class should be extendable but not modifiable).
Now, with your permission, let’s focus on the remarkable code section I want to delve into. We will create a base class that inherits from the command handler in CQRS, allowing us to perform transactional operations on classes derived from it. This class will intervene when our operation comes to the point of saving the data, and if there is no error, it will commit and save. If there is an error, it will roll back the operation.”
In the context of my own project, the IRequestHandler part is the custom mediator code that I have written:
public interface IYtRequestHandler<TRequest, TResponse> where TRequest : IYtRequest<TResponse>
{
Task<TResponse> Handle(TRequest request,CancellationToken cancellationToken);
}
And here is our RequestHandler class that will intervene when IYtRequestHandler is encountered. This class applies the decorator design pattern and has the ability to perform transaction operations:
public class TransactionalRequestHandlerDecorator<TRequest, TResponse> : IYtRequestHandler<TRequest, TResponse>
where TRequest : IYtRequest<TResponse>
{
private readonly IUnitOfWork _unitOfWork;
private readonly IYtRequestHandler<TRequest, TResponse> _innerHandler;
public TransactionalRequestHandlerDecorator(IUnitOfWork unitOfWork, IYtRequestHandler<TRequest, TResponse> innerHandler)
{
_unitOfWork = unitOfWork;
_innerHandler = innerHandler;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken)
{
// If the name of a specific dependency ends with the word "CommandHandler," perform the operation
if (_innerHandler.GetType().Name.EndsWith("CommandHandler"))
{
await using var transaction = await _unitOfWork.BeginTransactionAsync(cancellationToken);
try
{
var result = await _innerHandler.Handle(request, cancellationToken);
await _unitOfWork.CommitTransactionAsync(cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return result;
}
catch
{
await _unitOfWork.RollbackTransactionAsync(cancellationToken);
throw;
}
}
else
{
// For other types, do normal operation
return await _innerHandler.Handle(request, cancellationToken);
}
}
}
We can handle this situation differently by introducing a boolean property in our IRequest
interface. For commands, we can set this property to true, and for queries, we can set it to false. Then, we can control the behavior with a single if condition.
public interface IYtRequest<TResponse>
{
bool IsCommand { get; }
}
public class SomeCommandNameHere:IYtRequest<SomeCommandNameHereDto>
{
public bool IsCommand => true;
// ...
}
public class SomeQueryNameHere: IYtRequest<SomeQueryNameHereDto>
{
public bool IsCommand => false;
// ...
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken)
{
// Control the operation with the IsCommand property added to the IYtRequest<TResponse> interface
if (request.IsCommand)
{
await using var transaction = await _unitOfWork.BeginTransactionAsync(cancellationToken);
try
{
var result = await _innerHandler.Handle(request, cancellationToken);
await _unitOfWork.CommitTransactionAsync(cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return result;
}
catch
{
await _unitOfWork.RollbackTransactionAsync(cancellationToken);
throw;
}
}
else
{
// For other types, do normal operation
return await _innerHandler.Handle(request, cancellationToken);
}
}
We have completed the implementation of the Decorator design pattern. Now, what we need to do is resolve the dependency injection part here using Scrutor:
private static IServiceCollection RegisterServices(this IServiceCollection services, Assembly[] assemblies)
{
services.Scan(scan => scan
.FromAssemblies(assemblies)
.AddClasses(classes => classes.AssignableTo(typeof(IYtRequestHandler<,>)))
.AsImplementedInterfaces()
.WithTransientLifetime());
services.Scan(scan => scan
.FromAssemblies(assemblies)
.AddClasses(classes => classes.AssignableTo(typeof(IYtPipelineBehavior<,>)))
.AsImplementedInterfaces()
.WithTransientLifetime());
services.Scan(scan => scan
.FromAssemblies(assemblies)
.AddClasses(classes => classes.AssignableTo(typeof(IYtMediator)))
.AsImplementedInterfaces()
.WithTransientLifetime());
services.Scan(scan => scan
.FromAssemblies(assemblies)
.AddClasses(classes => classes.AssignableTo(typeof(TransactionalRequestHandlerDecorator<,>)))
.AsImplementedInterfaces()
.WithTransientLifetime());
services.Decorate(typeof(IYtRequestHandler<,>), typeof(TransactionalRequestHandlerDecorator<,>));
return services;
}
Here, I’m resolving all the classes I’ve written for my custom mediator using the ‘Scan’ method of Scrutor and specifying a lifetime for each. Finally, I define, through the ‘decorate’ method, that the extended class will work for classes derived from IYtRequestHandler, including the transaction method.
That’s all I have to share on this topic. I hope this article has been informative and productive for everyone.
To get in touch with me and explore my open-source projects, you can reach me at:
LinkedIn : https://www.linkedin.com/in/yigittanyel/
GitHub: https://github.com/yigittanyel