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 shortcutAlt + 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.