Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dotnet-output-caching-api

A production-ready ASP.NET Core Web API demonstrating Output Caching with named policies, tag-based eviction, vary-by-route, and vary-by-query strategies — all with a clean Product Catalog domain.

Visit CodingDroplets YouTube Patreon Buy Me a Coffee GitHub


🚀 Support the Channel — Join on Patreon

If this sample saved you time, consider joining our Patreon community. You'll get exclusive .NET tutorials, premium code samples, and early access to new content — all for the price of a coffee.

👉 Join CodingDroplets on Patreon

Prefer a one-time tip? Buy us a coffee ☕


🎯 What You'll Learn

  • The difference between Output Caching and Response Caching — and why Output Caching is superior for modern APIs
  • How to register named Output Cache policies with custom TTLs
  • How to vary cache entries by route value ({id}) and by query string (?name=Electronics)
  • How to use cache tags (products, category) for atomic bulk eviction
  • How to evict cache entries from inside a controller using IOutputCacheStore
  • How to write unit tests and integration tests that cover the full HTTP pipeline
  • Enterprise coding patterns: interface-based services, dependency injection, XML doc comments

🗺️ Architecture Overview

HTTP Client
    │
    ▼
┌─────────────────────────────────────────────────┐
│           ASP.NET Core Middleware Pipeline        │
│                                                   │
│  UseOutputCache()  ◄── CACHE HIT?                 │
│       │                    │                      │
│       │ Cache MISS         │ Cache HIT             │
│       ▼                    │                      │
│  UseRouting()              │                      │
│       │                    │                      │
│       ▼                    │                      │
│  ProductsController        │                      │
│       │                    │                      │
│       ▼                    │                      │
│  IProductService           │ ◄── Return cached    │
│  (in-memory store)         │     response         │
│       │                    │     directly         │
│       ▼                    │                      │
│  Response generated ───────┘                      │
│  + stored in cache                                │
└─────────────────────────────────────────────────┘
    │
    ▼
HTTP Response

Cache eviction flow (POST / DELETE):

Client: POST /api/products
         │
         ▼
ProductsController.Create()
         │
         ├── Saves product to IProductService
         │
         └── IOutputCacheStore.EvictByTagAsync("products")
                  │
                  └── All cache entries tagged "products" are invalidated
                      Next GET requests hit the controller and repopulate the cache

📋 Cache Policy Summary

Policy Name TTL Vary By Tags Applied Used On
Short 30 sec (available for extension)
Long 5 min products GET /api/products
VaryByCategory 30 sec name query param products, category GET /api/products/category
VaryById 5 min id route value products GET /api/products/{id}

📁 Project Structure

dotnet-output-caching-api/
├── dotnet-output-caching-api.sln
│
├── OutputCaching.Api/                    # Main Web API project
│   ├── Controllers/
│   │   └── ProductsController.cs         # CRUD endpoints with [OutputCache] attributes
│   ├── Models/
│   │   └── Product.cs                    # Product entity
│   ├── Policies/
│   │   └── CacheConstants.cs             # Policy names + tag constants (no magic strings)
│   ├── Services/
│   │   ├── IProductService.cs            # Service contract
│   │   └── ProductService.cs             # In-memory implementation
│   ├── Properties/
│   │   └── launchSettings.json           # Visual Studio launch config (Swagger UI)
│   └── Program.cs                        # DI, policy registration, middleware pipeline
│
└── OutputCaching.Tests/                  # xUnit test project
    ├── ProductServiceTests.cs            # Unit tests for ProductService
    └── ProductsControllerIntegrationTests.cs  # Integration tests via WebApplicationFactory

🛠️ Prerequisites

Requirement Version
.NET SDK 10.0+
IDE Visual Studio 2022 / JetBrains Rider / VS Code

Check your SDK: dotnet --version


⚡ Quick Start

# 1. Clone the repository
git clone https://github.com/codingdroplets/dotnet-output-caching-api.git
cd dotnet-output-caching-api

# 2. Build the solution
dotnet build -c Release

# 3. Run the API
cd OutputCaching.Api
dotnet run

# 4. Open Swagger UI
# http://localhost:5289/swagger

The browser opens automatically to the Swagger UI when launched from Visual Studio.


🔧 How It Works

Step 1 — Register Output Cache services

// Program.cs
builder.Services.AddOutputCache(options =>
{
    // Short-lived: 30-second TTL
    options.AddPolicy("Short", policy =>
        policy.Expire(TimeSpan.FromSeconds(30)));

    // Long-lived: 5-minute TTL
    options.AddPolicy("Long", policy =>
        policy.Expire(TimeSpan.FromMinutes(5)));

    // Vary-by-query: one cache entry per unique "name" query value
    options.AddPolicy("VaryByCategory", policy =>
        policy
            .Expire(TimeSpan.FromSeconds(30))
            .SetVaryByQuery("name"));

    // Vary-by-route: one cache entry per unique "id" route segment
    options.AddPolicy("VaryById", policy =>
        policy
            .Expire(TimeSpan.FromMinutes(5))
            .SetVaryByRouteValue("id"));
});

Step 2 — Add middleware (before routing)

// MUST come before app.MapControllers()
app.UseOutputCache();

Step 3 — Apply policy on endpoints with [OutputCache]

[HttpGet]
[OutputCache(PolicyName = "Long", Tags = ["products"])]
public IActionResult GetAll()
{
    return Ok(_productService.GetAll());
}

[HttpGet("{id:int}")]
[OutputCache(PolicyName = "VaryById", Tags = ["products"])]
public IActionResult GetById(int id) { ... }

Step 4 — Evict by tag after writes

[HttpPost]
public async Task<IActionResult> Create([FromBody] Product product, CancellationToken ct)
{
    var created = _productService.Add(product);

    // Immediately invalidate all cache entries tagged "products"
    await _cacheStore.EvictByTagAsync("products", ct);

    return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
}

📡 API Endpoints

Method Endpoint Description Status Codes
GET /api/products List all products (cached 5 min) 200
GET /api/products/{id} Get product by ID (cached 5 min per ID) 200, 404
GET /api/products/category?name={cat} Filter by category (cached 30 sec/cat) 200, 400
POST /api/products Add product + evict "products" cache 201, 400
DELETE /api/products/{id} Delete product + evict "products" cache 204, 404

🧪 Running Tests

dotnet test -c Release
Test Class Type Count
ProductServiceTests Unit 9
ProductsControllerIntegrationTests Integration 9
Total 18

All 18 tests cover: CRUD operations, edge cases (missing IDs, empty names), 400/404 responses, and full HTTP pipeline behavior via WebApplicationFactory<Program>.


🤔 Key Concepts

Output Caching vs Response Caching

Feature Output Caching Response Caching
Storage location Server memory (in-process) HTTP cache (proxy/browser)
Control Full server-side control Relies on HTTP headers (Cache-Control)
Tag-based eviction ✅ Yes (EvictByTagAsync) ❌ No
Vary by route/query ✅ Built-in (SetVaryByRouteValue) ⚠️ Via Vary header only
Works with POST/DELETE ✅ Can evict on mutation ❌ Only caches GET/HEAD
Introduced .NET 7 .NET Core 1.0

Why Tag-Based Eviction?

Tags let you group related cache entries and invalidate them all atomically:

GET /api/products         → tagged "products"
GET /api/products/1       → tagged "products"
GET /api/products/2       → tagged "products"
GET /api/products/category?name=Electronics → tagged "products", "category"

POST /api/products
  └── EvictByTagAsync("products")
       → ALL four entries above are invalidated in one call

Without tags, you'd need to track and evict every cache key individually.


🏷️ Technologies Used

  • ASP.NET Core 10 — Web API framework
  • Output Caching (Microsoft.AspNetCore.OutputCaching) — Server-side response cache
  • Swashbuckle / Swagger UI — API documentation and testing
  • xUnit — Unit and integration testing framework
  • WebApplicationFactory — In-process integration test host

📚 References


📄 License

This project is licensed under the MIT License — see LICENSE for details.


🔗 Connect with CodingDroplets

Platform Link
🌐 Website https://codingdroplets.com/
📺 YouTube https://www.youtube.com/@CodingDroplets
🎁 Patreon https://www.patreon.com/CodingDroplets
☕ Buy Me a Coffee https://buymeacoffee.com/codingdroplets
💻 GitHub http://github.com/codingdroplets/

Want more samples like this? Support us on Patreon or buy us a coffee ☕ — every bit helps keep the content coming!

About

ASP.NET Core Web API demonstrating Output Caching with named policies, tag-based eviction, vary-by-route, and vary-by-query cache strategies. Includes 18 tests. Built with .NET 10.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages