Decorator Pattern In ASP.NET Core

Decorator Pattern In ASP.NET Core

2 min read ·

Thank you to our sponsors who keep this newsletter free to the reader:

This week's issue is sponsored by QuadSpinner.

Let's imagine we have an existing Repository implementation, and we want to introduce caching to reduce the load on the database.

How can we achieve this without changing the original Repository implementation?

Decorator pattern is a structural design pattern that allows you to introduce new behavior to an existing class, without modifying the original class in any way.

I'll show you how you can implement this with the ASP.NET Core DI container.

How To Implement The Decorator Pattern

We'll start with an existing MemberRepository implementation that implements the IMemberRepository interface.

It has only one method, which loads the Member from the database.

Here's what the implementation looks like:

public interface IMemberRepository
{
    Member GetById(int id);
}

public class MemberRepository : IMemberRepository
{
    private readonly DatabaseContext _dbContext;

    public MemberRepository(DatabaseContext dbContext)
    {
        _dbContext = dbContext;
    }

    public Member GetById(int id)
    {
        return _dbContext
            .Set<Member>()
            .First(member => member.Id == id);
    }
}

We want to introduce caching to the MemberRepository implementation without modifying the existing class.

To achieve this, we can use the Decorator pattern and create a wrapper around our MemberRepository implementation.

We can create a CachingMemberRepository that will have a dependency on IMemberRepository.

public class CachingMemberRepository : IMemberRepository
{
    private readonly IMemberRepository _repository;
    private readonly IMemoryCache _cache;

    public CachingMemberRepository(
        IMemberRepository repository,
        IMemoryCache cache)
    {
        _repository = repository;
        _cache = cache;
    }

    public Member GetById(int id)
    {
        string key = $"members-{id}";

        return _cache.GetOrCreate(
            key,
            entry => {
                entry.SetAbsouluteExpiration(
                    TimeSpan.FromMinutes(5));

                return _repository.GetById(id);
            });
    }
}

Now I'm going to show you the power of ASP.NET Core DI.

We will configure the IMemberRepository to resolve an instance of CachingMemberRepository, while it will receive the MemberRepository instance as its dependency.

Configuring The Decorator In ASP .NET Core DI

For the DI container to be able to resolve IMemberRepository as CachingMemberRepository, we need to manually configure the service.

We can use the overload that exposes a service provider, that we will use to resolve the services required to construct a MemberRepository.

Here's what the configuration would look like:

services.AddScoped<IMemberRepository>(provider => {
    var context = provider.GetService<DatabaseContext>();
    var cache = provider.GetService<IMemoryCache>();

    return new CachingRepository(
         new MemberRepository(context),
         cache);
});

Now you can inject the IMemberRepository, and the DI will be able to resolve an instance of CachingMemberRepository.

Configuring The Decorator With Scrutor

If the previous approach seems cumbersome to you and like a lot of manual work - that's because it is.

However, there is a simpler way to achieve the same behavior.

We can use the Scrutor library to register the decorator:

services.AddScoped<IMemberRepository, MemberRepository>();

services.Decorate<IMemberRepository, CachingMemberRepository>();

Scrutor exposes the Decorate method. The call to Decorate will register the CachingMemberRepository while ensuring that it receives the expected MemberRepository instance as its dependency.

I think this approach is much simpler, and it's what I use in my projects.


Whenever you're ready, there are 4 ways I can help you:

  1. Modular Monolith Architecture (COMING SOON): This in-depth course will transform the way you build monolith systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario. Join the waitlist here.
  2. Pragmatic Clean Architecture: This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. Join 2,500+ students here.
  3. Patreon Community: Join a community of 1,050+ engineers and gain access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses. Join 1,050+ engineers here.
  4. Promote yourself to 45,000+ subscribers by sponsoring this newsletter.

Become a Better .NET Software Engineer

Join 45,000+ engineers who are improving their skills every Saturday morning.