Decorator pattern in ASP.NET Core: adding HybridCache caching to a C# service without changes

.NET Architecture & Design Patterns November 13, 2026 csharp

With a Decorator: a class that implements the same interface, wraps the original and adds behavior (caching, logging, metrics, retry).

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ======================================================================
// Q: How would you add caching to a service without changing it?
//
// With a Decorator: a class that implements the same interface, wraps the
// original and adds behavior (caching, logging, metrics, retry). The original
// code doesn't change (Open/Closed).
// ======================================================================

public interface IPriceService
{
    Task<decimal> GetPriceAsync(string sku, int customerId, CancellationToken ct);
}

public class CachedPriceService(IPriceService inner, HybridCache cache) : IPriceService
{
    public async Task<decimal> GetPriceAsync(string sku, int customerId, CancellationToken ct) =>
        await cache.GetOrCreateAsync(
            $"price:{customerId}:{sku}",                      // price per customer (B2B price lists)
            async token => await inner.GetPriceAsync(sku, customerId, token),
            cancellationToken: ct);
}

// Registration without a library
builder.Services.AddHybridCache();                                    // .NET 9
builder.Services.AddScoped<ErpPriceService>();
builder.Services.AddScoped<IPriceService>(sp => new CachedPriceService(
    sp.GetRequiredService<ErpPriceService>(),
    sp.GetRequiredService<HybridCache>()));

// or with Scrutor:  builder.Services.Decorate<IPriceService, CachedPriceService>();