Understanding the Decorator Pattern in C# with .NET 8 Minimal API

In software design, the Decorator pattern is a structural pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. In this article, we'll explore the Decorator pattern in the context of a .NET 8 Minimal API using C#, along with the Scrutor package.

Installing Packages

Before we dive into the code, let's install the necessary packages using the Package Manager Console:

Install-Package Bogus
Install-Package Scrutor

Person Class

We have a Person class that represents a person with properties like Id, Name, Age, and Email. Additionally, there's a static method to generate fake people using the Bogus library.


public record Person(int Id, string Name, int Age, string Email)
{
    public static List<Person> GenerateFakePeople(int numberOfPeople)
    {
        var id = 1;
        var faker = new Faker<Person>()
            .CustomInstantiator(f =>
            new Person(id++, f.Person.FullName, f.Random.Int(18, 60), f.Person.Email));

        var people = faker.Generate(numberOfPeople);

        return people;
    }
}

IRepository Interface

Next, we have an IRepository interface that defines a method to get a collection of Person objects.

public interface IRepository
{
	IEnumerable<Person> Get();
}
	

PersonRepository Class

A concrete implementation of the IRepository interface is the PersonRepository class, responsible for retrieving a collection of fake people.

public class PersonRepository : IRepository
{
    public IEnumerable<Person> Get()
    {
        return Person.GenerateFakePeople(10);
    }
}

CacheRepository Class

Now, let's introduce the Decorator pattern. We create a CacheRepository class that implements the IRepository interface and takes another IRepository instance as a dependency. It uses the IMemoryCache to cache the result of the Get method for a specified duration.

using Microsoft.Extensions.Caching.Memory;

public class CacheRepository : IRepository
{
    private readonly IMemoryCache _cache;
    private readonly IRepository _repo;

    public CacheRepository(IMemoryCache cache, IRepository repo)
    {
        _cache = cache;
        _repo = repo;
    }

    public IEnumerable<Person> Get()
    {
        return _cache.GetOrCreate("people", f =>
        {
            f.SetAbsoluteExpiration(TimeSpan.FromSeconds(10));
            return _repo.Get();
        }) ?? Enumerable.Empty<Person>();
    }
}

Program.cs

In the Program.cs file, we set up our Minimal API using .NET 8. We register services, including the MemoryCache, PersonRepository, and use Scrutor to decorate the IRepository with the CacheRepository. Finally, we define a simple endpoint that returns the result of repo.Get().


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMemoryCache();
builder.Services.AddScoped<IRepository, PersonRepository>();
builder.Services.Decorate<IRepository, CacheRepository>();

var app = builder.Build();
app.MapGet("/", (IRepository repo) => repo.Get());
app.Run();

Steps to test

Step 1: Open Endpoints Explorer

  1. Open the Visual Studio IDE.
  2. Navigate to View -> Other Windows -> Endpoints Explorer or use the shortcut Alt + E, Alt + E.
  3. In the Endpoints Explorer, expand and select the GET endpoint relevant to your API.

Step 2: Generate .http file

  1. Right-click on the selected GET endpoint.
  2. Choose the Generate option from the context menu. This action will generate an associated .http file containing the details of the request.

Step 3: Set Breakpoints in PersonRepository Get Method

  1. Open the code for your PersonRepository.
  2. Locate the Get method.
  3. Set a breakpoint at the beginning of the Get method. This will pause the execution of the code when the breakpoint is hit during debugging.

Step 4: Set Breakpoints in CacheRepository Get Method

  1. Similarly, open the code for your CacheRepository.
  2. Locate the Get method.
  3. Set a breakpoint at the beginning of the Get method to understand how the caching mechanism works.

Step 5: Run the Application

  1. Run your .NET application in Debug mode.
  2. Observe the Visual Studio debugger will be active, indicating that breakpoints can be hit during execution.

Step 6: Click "Send Request" in the .http File

  1. Navigate to the generated .http file.
  2. Click on the "Send Request" option within the file. This will trigger the API request associated with the GET endpoint.
  3. Observe the debugger will hit the breakpoint in the CacheRepository and PersonRepository Get method.
  4. Wait for 10 seconds to simulate the caching mechanism, observing that the breakpoint in the PersonRepository Get method will not be hit during this period.

Summary

In this article, we explored the Decorator pattern in the context of a .NET 8 Minimal API using C#. We created a basic example with a Person class, an IRepository interface, and two concrete implementations: PersonRepository and CacheRepository. The Decorator pattern allows us to dynamically add caching behavior to our repository without modifying existing code, demonstrating the flexibility and extensibility of this design pattern.

Leveraging Endpoints Explorer in .NET 8 Minimal API

In this article, we'll explore how to leverage the Endpoints Explorer in a .NET 8 Minimal API application for debugging and sending re...