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
- Open the Visual Studio IDE.
- Navigate to
View -> Other Windows -> Endpoints Explorer or use the shortcut Alt + E, Alt + E.
- In the Endpoints Explorer, expand and select the
GET endpoint relevant to your API.
Step 2: Generate .http file
- Right-click on the selected
GET endpoint.
- 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
- Open the code for your
PersonRepository.
- Locate the
Get method.
- 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
- Similarly, open the code for your
CacheRepository.
- Locate the
Get method.
- Set a breakpoint at the beginning of the
Get method to understand how the caching mechanism works.
Step 5: Run the Application
- Run your .NET application in Debug mode.
- 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
- Navigate to the generated
.http file.
- Click on the "Send Request" option within the file. This will trigger the API request associated with the GET endpoint.
- Observe the debugger will hit the breakpoint in the CacheRepository and PersonRepository Get method.
- 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.