blog
Ottorino Bruni  

How to Use Response Caching for Faster Results in ASP.NET Core Minimal API

Introduction

In modern web applications, performance is a key factor for both user satisfaction and system efficiency. One highly effective technique to enhance performance is response caching. With response caching, browsers or other clients store a copy of a server’s response locally. When the same resource is requested again, the cached version is served instantly. This saves time by eliminating unnecessary communication with the server and avoids duplicate work on the server side.

ASP.NET Core provides several powerful caching mechanisms tailored to different scenarios, including in-memory caching, distributed caching, and response caching. In this article, we’ll focus specifically on Response Caching in the context of Minimal APIs. We’ll explore how it works, why it improves performance, and see a step-by-step example of implementing it effectively.

In ASP.NET Core, we have several caching mechanisms available, but today we’ll focus on Response Caching, particularly in the context of Minimal APIs.

What is Response Caching?

Response caching is a way to store the server’s response to a request so that the same data can be reused without requiring the server to execute the same logic repeatedly. This technique benefits both the client and the server by:

  • Reducing server load: The server doesn’t need to process the same request multiple times.
  • Improving client performance: The client retrieves the data faster since it’s served from the cache.
  • Saving bandwidth: Cached responses minimize data transfer, especially for large payloads.

ASP.NET Core’s Response Caching Middleware provides the functionality to cache HTTP responses. However, it works best for idempotent GET requests, where the response doesn’t change unless the resource itself changes.

 If you’re interested in further optimizing your caching strategies, check out my previous articles on How to Implement In-Memory Caching in .NET Console App Using C# and How to Implement Redis Cache in .NET using Minimal APIs and C#. Both provide valuable techniques that complement the output caching insights shared here.

How to Use Response Caching for Faster Results in ASP.NET Core Minimal API

Understanding Client-Side vs Server-Side Caching

Before diving into the implementation, let’s clarify the key differences between client-side and server-side caching:

Client-Side Response Caching

  • Stores responses in the client’s browser or proxy servers
  • Controlled through HTTP headers (Cache-Control, Age, Vary, etc.)
  • Reduces network traffic by avoiding unnecessary server requests
  • Ideal for static or semi-static data that doesn’t change frequently
  • No server memory consumption

Server-Side Caching

  • Stores data in server memory or distributed cache (Redis, SQL Server)
  • Requires server resources to maintain the cache
  • Provides more control over cached data
  • Suitable for dynamic data that needs server-side processing
  • For more details about server-side caching, check my previous articles on In-Memory and Distributed caching

Brief Explanation of HTTP-Based Response Caching

HTTP-based response caching, defined in RFC 9111, describes how internet caches (such as client-side caches, proxy servers, or reverse proxies) should behave to improve the efficiency of web communication. The Cache-Control HTTP header plays a crucial role in controlling caching behavior, enabling caches to store responses and reuse them to reduce latency, bandwidth usage, and load on servers.

Key Concepts in HTTP Caching

  1. Cache-Control Directives:
    These are instructions included in HTTP headers that guide how requests and responses should be cached. Here are some common directives:
    • public: The response can be stored and reused by any cache (e.g., browsers, shared proxies).
    • private: Limits caching to a single client. Shared caches (like proxies) must not store the response.
    • max-age: Defines how many seconds a response can remain fresh in the cache. Example:
      • max-age=60: Cache the response for 60 seconds.
      • max-age=2592000: Cache the response for 30 days (1 month).
    • no-cache:
      • On requests: The cache must revalidate the response with the origin server before serving it.
      • On responses: Clients must validate the response with the origin server before reuse.
    • no-store: Prohibits the cache from storing any part of the request or response.
  2. Other Cache Headers:
    Beyond Cache-Control, additional headers influence caching:
    • Age: Indicates the time (in seconds) since the response was generated or validated at the origin server.
    • Expires: Specifies an expiration date/time after which the response is considered stale.
    • Pragma: Used for backward compatibility with HTTP/1.0 caches (e.g., setting no-cache). Ignored if Cache-Control is present.
    • Vary: Ensures cached responses are served correctly by requiring certain request headers to match. For example, if Vary: Accept-Language is set, separate cached responses are stored for each language.

How Does Response Caching Work in ASP.NET Core?

When you enable response caching in ASP.NET Core, the server adds specific HTTP headers to the response. These caching headers inform clients and any intermediate caches (e.g., CDN providers) how to cache the response.

Key headers involved include:

  • Cache-Control: Specifies how caching should work (e.g., whether caching is allowed, duration, etc.).
  • Expires: Indicates when the cached response should expire.
  • Vary: Modifies how caching is handled for requests with different headers (e.g., caching separate versions of the response based on Accept-Language).

For example, when a client makes a GET request, a cached response can be served without involving the server if the headers show it is still valid.

Example: Implement Response Caching in ASP.NET Core Minimal API

Disclaimer: This example is intended for educational purposes only. While it demonstrates key concepts, there are more efficient ways to write and optimize this code in real-world applications. Consider this a starting point for learning, and always aim to follow best practices and refine your implementation.

In this example, we’ll demonstrate how to implement Response Caching in an ASP.NET Core Minimal API. You’ll see how to:

  • Introduce variations in caching behavior using the VaryByHeader.
  • Configure the ResponseCache attribute to cache GET responses for 10 seconds.
  • Use middleware to inspect cache behavior and provide custom headers.

Prerequisites

Before starting, make sure you have the following installed:

  • .NET SDK: Download and install the .NET SDK if you haven’t already.
  • Visual Studio Code (VSCode): Install Visual Studio Code for a lightweight code editor.
  • C# Extension for VSCode: Install the C# extension for VSCode to enable C# support.

Step 1: Create a Minimal API

dotnet new webapi -minimal -n ResponseCachingApi
cd ResponseCachingApi

Add the Microsoft.AspNetCore.ResponseCaching NuGet package:

dotnet add package Microsoft.AspNetCore.ResponseCaching
How to Use Response Caching – Microsoft.AspNetCore.ResponseCaching

Step 2: Set Up Output Caching

The first step is to integrate Output Caching Middleware into your ASP.NET Core Minimal API pipeline. This middleware handles caching logic and serves responses directly from the cache when possible, bypassing the endpoint logic entirely.

To enable Output Caching:

  • Register the OutputCache service within the builder.Services.AddOutputCache() method.
  • Call app.UseOutputCache() in the middleware pipeline to enable the caching system.

Here’s how the setup looks inside the Program.cs file:

builder.Services.AddOutputCache();   // Register output caching service
...
app.UseOutputCache();                // Enable the middleware

 Step 3: Define the Endpoint

Our example demonstrates a /quotes endpoint that returns a collection of motivational quotes, along with a request counter and the current server time for demonstration purposes. The endpoint is configured to:

  • Increment the request counter on every call.
  • Enable output caching for 5 seconds, so repeated calls within that duration will reuse the cached response.

Here’s the endpoint definition:

app.MapGet("/quotes", () =>
{
    requestCounter++;

    var response = new
    {
        RequestNumber = requestCounter,    // Incremented for every request
        ServerTime = DateTime.UtcNow,     // Tracks current time (cached output will not update during cache duration)
        Quotes = quotes,                  // Array of motivational quotes
        CacheInfo = "This response will be cached for 5 seconds"
    };

    return Results.Ok(response);          // Return the response
})
.CacheOutput(options =>
{
    options.Expire(TimeSpan.FromSeconds(5));  // Cache response for 5 seconds
    options.Tag("User-Agent"); // Ensure caching behavior varies by User-Agent (optional)
})
.WithName("GetQuotes");

Step 4: Enable Output Caching for the Endpoint

The CacheOutput method is used to enable caching on the specific endpoint. For this example:

  • 5-Second Expiry Time: We configure the cache to expire after 5 seconds so that the response is reused for subsequent requests during that period.
  • Vary by User-Agent: We use the Tag("User-Agent") option, ensuring that separate caches are created for different user agents (like browsers or API tools).

Here’s how you configure it:

.CacheOutput(options => { 
  // Set cache expiry duration
  options.Expire(TimeSpan.FromSeconds(5));  

  // Vary cache by the User-Agent header 
  options.Tag("User-Agent");                
 })
How to Use Response Caching – Source Code

Step 5: Overall Pipeline Setup

Here’s the full setup of your Program.cs file, including middleware and endpoint configuration:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddOutputCache();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseOutputCache();
app.UseHttpsRedirection();

// In-memory storage for demonstration (not thread-safe, for demo only)
var quotes = new[]
{
    "The best way to get started is to quit talking and begin doing.",
    "Don't let yesterday take up too much of today.",
    "It's not whether you get knocked down, it's whether you get up.",
    "The optimist sees opportunity in every difficulty."
};

// Request counter (for demonstration purposes)
int requestCounter = 0;

app.MapGet("/quotes", () =>
{
    requestCounter++;

    var response =  new
    {
        RequestNumber = requestCounter,
        ServerTime = DateTime.UtcNow,
        Quotes = quotes,
        CacheInfo = "This response will be cached for 5 seconds"
    };

    return Results.Ok(response);
})
.CacheOutput(options =>
{
    options.Expire(TimeSpan.FromSeconds(5));
    options.Tag("User-Agent");
})
.WithName("GetQuotes");

app.Run();

Explanation of How Output Caching Works

When you enable output caching using the CacheOutput method or the [OutputCache] attribute, the Output Caching Middleware intercepts requests and stores their responses in the cache for a specified duration.

  1. First Request: When a client requests the /quotes endpoint for the first time, the application logic is executed, and the response is generated fresh. The middleware stores this response in the cache.
    Example:
    • RequestNumber1
    • ServerTime2025-01-10T15:00:00
  2. Subsequent Requests (Cached): For all requests within 5 seconds of the first request, the middleware serves the cached response without executing the endpoint logic again. Example:
    • The RequestNumber remains 1 (cached value).
    • The ServerTime remains 2025-01-10T15:00:00.
  3. Cache Expiry: After 5 seconds, the cache expires, and the next request is processed from scratch. The endpoint logic runs and generates a new value for RequestNumber and ServerTime.
How to Use Response Caching – Test Minimal API

Implementing Caching in Controller-Based APIs

For a Controller-based API, caching works differently. Response Caching Middleware is used instead of the newer Output Caching Middleware

Feature Minimal APIs (Output Caching) Controller-Based APIs (Response Caching)
Caching Middleware Requires OutputCache Middleware Requires ResponseCaching Middleware
Caching Setup .CacheOutput() or [OutputCache] attribute [ResponseCache] attribute
Response Variation Flexible with .Tag(...), VaryByQuery, or other custom settings Limited to VaryByHeader, no custom variation support
Best Use Case New apps with better control of server-side caching behavior Legacy or controller-based applications

Final Thoughts

Output Caching in ASP.NET Core is an easy and highly effective way to improve the performance of APIs by caching entire responses on the server side. This dramatically reduces server workload for frequently accessed resources like static data or rarely-changing endpoints.

By following the steps in this guide, you can add fine-tuned output caching to your Minimal APIs, ensuring optimized performance and a faster user experience. You can also build upon this foundation by varying cached responses using headers, query parameters, or other keys, depending on your application’s needs.

For more details on output caching, check out the official ASP.NET Core Documentation.

If you think your friends or network would find this article useful, please consider sharing it with them. Your support is greatly appreciated.

Thanks for reading!

🚀 Discover CodeSwissKnife, your all-in-one, offline toolkit for developers!

Click to explore CodeSwissKnife 👉

Leave A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.