Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions aspnetcore/includes/response-caching-mid.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
The Response caching middleware:

* Enables caching server responses based on [HTTP cache headers](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). Implements the standard HTTP caching semantics. Caches based on HTTP cache headers like proxies do.
* Is typically not beneficial for UI apps such as Razor Pages because browsers generally set request headers that prevent caching. Output caching is being considered for the next version of ASP.NET Core, which will benefit UI apps. With output caching, configuration decides what should be cached independently of HTTP headers. For more information, see [this GitHub issue](https://github.com/dotnet/aspnetcore/issues/27387).
* Is typically not beneficial for UI apps such as Razor Pages because browsers generally set request headers that prevent caching. [Output caching](xref:performance/caching/output), which is available in ASP.NET Core 7.0 and later, benefits UI apps. With output caching, configuration decides what should be cached independently of HTTP headers.
* May be beneficial for public GET or HEAD API requests from clients where the [Conditions for caching](xref:performance/caching/middleware#cfc) are met.

Use [Fiddler](https://www.telerik.com/fiddler), [Postman](https://www.getpostman.com/), or another tool that can explicitly set request headers. Setting headers explicitly is preferred for testing caching. For more information, see [Troubleshooting](xref:performance/caching/middleware#troubleshooting)
To test response caching, use [Fiddler](https://www.telerik.com/fiddler), [Postman](https://www.getpostman.com/), or another tool that can explicitly set request headers. Setting headers explicitly is preferred for testing caching. For more information, see [Troubleshooting](xref:performance/caching/middleware#troubleshooting).
155 changes: 155 additions & 0 deletions aspnetcore/performance/caching/output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
title: Output caching middleware in ASP.NET Core
author: tdykstra
description: Learn how to configure and use output caching middleware in ASP.NET Core.
monikerRange: '>= aspnetcore-7.0'
ms.author: riande
ms.custom: mvc
ms.date: 10/26/2022
uid: performance/caching/output
---
# Output caching middleware in ASP.NET Core

By [Tom Dykstra](https://github.com/tdykstra)

:::moniker range=">= aspnetcore-7.0"

This article explains how to configure output caching middleware in an ASP.NET Core app. For an introduction to output caching, see [Output caching](xref:performance/caching/overview#output-caching).

The output caching middleware can be used in all types of ASP.NET Core apps: Minimal API, Web API with controllers, MVC, and Razor Pages. The sample app is a Minimal API, but every caching feature it illustrates is also supported in the other app types.

## Add the middleware to the app

Add the output caching middleware to the service collection by calling <xref:Microsoft.Extensions.DependencyInjection.OutputCacheServiceCollectionExtensions.AddOutputCache%2A>.

Add the middleware to the request processing pipeline by calling <xref:Microsoft.AspNetCore.Builder.OutputCacheApplicationBuilderExtensions.UseOutputCache%2A>.

> [!NOTE]
> * In apps that use [CORS middleware](xref:security/cors), `UseOutputCache` must be called after <xref:Microsoft.AspNetCore.Builder.CorsMiddlewareExtensions.UseCors%2A>.
> * In Razor Pages apps and apps with controllers, `UseOutputCache` must be called after `UseRouting`.
> * Calling `AddOutputCache`and `UseOutputCache` doesn't start caching behavior, it makes caching available. Caching response data must be configured as shown in the following sections.

## Configure one endpoint or page

For minimal API apps, configure an endpoint to do caching by calling [`CacheOutput`](xref:Microsoft.Extensions.DependencyInjection.OutputCacheConventionBuilderExtensions.CacheOutput%2A), or by applying the [`[OutputCache]`](xref:Microsoft.AspNetCore.OutputCaching.OutputCacheAttribute) attribute, as shown in the following examples:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="oneendpoint":::

For apps with controllers, apply the `[OutputCache]` attribute to the action method. For Razor Pages apps, apply the attribute to the Razor page class.

## Configure multiple endpoints or pages

Create *policies* when calling `AddOutputCaching` to specify caching configuration that applies to multiple endpoints. A policy can be selected for specific endpoints, while a base policy provides default caching configuration for a collection of endpoints.

The following highlighted code configures caching for all of the app's endpoints, with expiration time of 10 seconds. If an expiration time isn't specified, it defaults to one minute.

:::code language="csharp" source="output/samples/7.x/Program.cs" id="policies1" highlight="3":::

The following highlighted code creates two policies, each specifying a different expiration time. Selected endpoints can use the 20 second expiration, and others can use the 30 second expiration.

:::code language="csharp" source="output/samples/7.x/Program.cs" id="policies1" highlight="4-5":::

You can select a policy for an endpoint when calling the `CacheOutput` method or using the `[OutputCache]` attribute:
Comment thread
tdykstra marked this conversation as resolved.

:::code language="csharp" source="output/samples/7.x/Program.cs" id="selectpolicy":::

For apps with controllers, apply the `[OutputCache]` attribute to the action method. For Razor Pages apps, apply the attribute to the Razor page class.

## Default output caching policy

By default, output caching follows these rules:

* Only HTTP 200 responses are cached.
* Only HTTP GET or HEAD requests are cached.
* Responses that set cookies aren't cached.
* Responses to authenticated requests aren't cached.

The following code applies all of the default caching rules to all of an app's endpoints:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="policies3":::

The following code removes these defaults while applying caching to all of an app's endpoints:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="policies4":::


You can override these defaults.
Comment thread
tdykstra marked this conversation as resolved.

## Specify the cache key

By default, every part of the URL is included as the key to a cache entry, that is, the scheme, host, port, path, and query string. However, you might want to explicitly control the cache key. For example, suppose you have an endpoint that returns a unique response only for each unique value of the `culture` query string. Variation in other parts of the URL, such as other query strings, shouldn't result in different cache entries. You can specify such rules in a policy, as shown in the following highlighted code:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="policies2" highlight="7":::

You can then select the `VaryByQuery` policy for an endpoint:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="selectquery":::

Here are some of the options for controlling the cache key:

* <xref:Microsoft.AspNetCore.OutputCaching.OutputCachePolicyBuilder.SetVaryByQuery%2A> - Specify one or more query string names to add to the cache key.
* <xref:Microsoft.AspNetCore.OutputCaching.OutputCachePolicyBuilder.SetVaryByHeader%2A> - Specify one or more HTTP headers to add to the cache key.
* <xref:Microsoft.AspNetCore.OutputCaching.OutputCachePolicyBuilder.VaryByValue%2A>- Specify a value to add to the cache key. The following example uses a value that indicates whether the current server time in seconds is odd or even. A new response is generated only when the number of seconds goes from odd to even or even to odd.

:::code language="csharp" source="output/samples/7.x/Program.cs" id="varybyvalue":::

For more options, see the <xref:Microsoft.AspNetCore.OutputCaching.OutputCachePolicyBuilder> class.

## Cache revalidation

Cache revalidation means the server can return a `304 Not Modified` HTTP status code instead of the full response body. This status code informs the client that the response to the request is unchanged from what the client previously received.

The following code illustrates the use of an [`Etag`](https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag) header to enable cache revalidation. If the client sends an [`If-None-Match`](https://developer.mozilla.org/docs/Web/HTTP/Headers/If-None-Match) header with the etag value of an earlier response, and the cache entry is fresh, the server returns [304 Not Modified](https://developer.mozilla.org/docs/Web/HTTP/Status/304) instead of the full response:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="etag":::

Another way to do cache revalidation is to check the date of the cache entry creation compared to the date requested by the client. When the request header `If-Modified-Since` is provided, output caching returns 304 if the cached entry is older and isn't expired.

Cache revalidation is automatic in response to these headers sent from the client. No special configuration is required on the server to enable this behavior, aside from enabling output caching.

## Use tags to evict cache entries

You can use tags to identify a group of endpoints and evict all cache entries for the group. For example, the following code creates a pair of endpoints whose URLs begin with "blog", and tags them "tag-blog":
Comment thread
tdykstra marked this conversation as resolved.

:::code language="csharp" source="output/samples/7.x/Program.cs" id="tagendpoint":::

An alternative way to assign tags for the same pair of endpoints is to define a base policy that applies to endpoints that begin with `blog`:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="policies2" highlight="3-5":::

Another alternative is to call `MapGroup`:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="taggroup":::

In the preceding tag assignment examples, both endpoints are identified by the `tag-blog` tag. You can then evict the cache entries for those endpoints with a single statement that references that tag:
Comment thread
tdykstra marked this conversation as resolved.

:::code language="csharp" source="output/samples/7.x/Program.cs" id="evictbytag":::

With this code, an HTTP POST request sent to `https://localhost:<port>/purge/tag-blog` will evict cache entries for these endpoints.

You might want a way to evict all cache entries for all endpoints. To do that, create a base policy for all endpoints as the following code does:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="policies2" highlight="6":::

This base policy enables you to use the "tag-all" tag to evict everything in cache.

## Disable resource locking

By default, resource locking is enabled to mitigate the risk of [cache stampede and thundering herd](https://en.wikipedia.org/wiki/Thundering_herd_problem). For more information, see [Output Caching](xref:performance/caching/overview#output-caching).

To disable resource locking, call [SetLocking(false)](xref:Microsoft.AspNetCore.OutputCaching.OutputCachePolicyBuilder.SetLocking%2A) while creating a policy, as shown in the following example:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="policies2" highlight="9":::

The following example selects the no-locking policy for an endpoint:

:::code language="csharp" source="output/samples/7.x/Program.cs" id="selectnolock":::

## See also

* <xref:performance/caching/overview>
* <xref:fundamentals/startup>
* <xref:fundamentals/middleware/index>
* <xref:fundamentals/change-tokens>

:::moniker-end
17 changes: 17 additions & 0 deletions aspnetcore/performance/caching/output/samples/7.x/Gravatar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

public static class Gravatar
{
public static async Task WriteGravatar(HttpContext context)
{
const string type = "monsterid"; // identicon, monsterid, wavatar
const int size = 200;
var hash = Guid.NewGuid().ToString("n");

context.Response.StatusCode = 200;
context.Response.ContentType = "text/html";
await context.Response.WriteAsync($"<img src=\"https://www.gravatar.com/avatar/{hash}?s={size}&d={type}\"/>");
await context.Response.WriteAsync($"<pre>Generated at {DateTime.Now:hh:mm:ss.ff}</pre>");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.0-preview.6.22330.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>

</Project>
126 changes: 126 additions & 0 deletions aspnetcore/performance/caching/output/samples/7.x/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#define Version4
using Microsoft.AspNetCore.OutputCaching;
using System.Globalization;

namespace OCMinimal;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddAuthorization();

#if Version1
//<policies1>
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(10)));
options.AddPolicy("Expire20", builder => builder.Expire(TimeSpan.FromSeconds(20)));
options.AddPolicy("Expire30", builder => builder.Expire(TimeSpan.FromSeconds(30)));
});
//</policies1>
#endif
#if Version2
//<policies2>
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
//</policies2>
#endif
#if Version3
//<policies3>
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache());
});
//</policies3>
#endif
#if Version4
//<policies4>
builder.Services.AddOutputCache();
//</policies4>
#endif
var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseOutputCache();
app.UseAuthorization();

app.MapGet("/", Gravatar.WriteGravatar);

//<oneendpoint>
app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) => Gravatar.WriteGravatar(context));
//</oneendpoint>

//<selectpolicy>
app.MapGet("/20", Gravatar.WriteGravatar).CacheOutput("Expire20");
app.MapGet("/30", [OutputCache(PolicyName = "Expire30")] (context) => Gravatar.WriteGravatar(context));
//</selectpolicy>

//<selectquery>
app.MapGet("/query", Gravatar.WriteGravatar).CacheOutput("Query");
//</selectquery>

//<varybyvalue>
app.MapGet("/varybyvalue", Gravatar.WriteGravatar)
.CacheOutput(c => c.VaryByValue((context) =>
new KeyValuePair<string, string>(
"time", (DateTime.Now.Second % 2)
.ToString(CultureInfo.InvariantCulture))));
//</varybyvalue>

//<etag>
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(context);

}).CacheOutput();
//</etag>


// <tagendpoint>
app.MapGet("/blog", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog")); ;
app.MapGet("/blog/post/{id}", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog")); ;
// </tagendpoint>

// <taggroup>
var blog = app.MapGroup("blog")
.CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);
// </taggroup>

// <taggroupoverride>
blog.MapGet("/post/{id}", Gravatar.WriteGravatar)
.CacheOutput(x => x.Tag("tag-blog", "tag-blog-post-id"));
// </taggroupoverride>

// <evictbytag>
app.MapPost("/purge/{tag}", async (IOutputCacheStore cache, string tag) =>
{
await cache.EvictByTagAsync(tag, default);
});
// </evictbytag>

// <selectnolock>
app.MapGet("/nolock", Gravatar.WriteGravatar).CacheOutput("NoLock");
// </selectnolock>

app.Run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
28 changes: 26 additions & 2 deletions aspnetcore/performance/caching/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ author: rick-anderson
description: Overview of caching in ASP.NET Core
monikerRange: '>= aspnetcore-3.1'
ms.author: riande
ms.date: 1/11/2022
ms.date: 08/12/2022
uid: performance/caching/overview
---
# Overview of caching in ASP.NET Core
Expand Down Expand Up @@ -37,4 +37,28 @@ For more information, see <xref:mvc/views/tag-helpers/builtin-th/distributed-cac

## Response caching

[!INCLUDE[](~/includes/response-caching-mid.md)]
[!INCLUDE[](~/includes/response-caching-mid.md)]

## Output caching

The output caching middleware enables caching of HTTP responses. Output caching differs from [response caching](#response-caching) in the following ways:

* The caching behavior is configurable on the server.

Response caching behavior is defined by HTTP headers. For example, when you visit a website with Chrome or Edge, the browser automatically sends a `Cache-control: max-age=0` header. This header effectively disables response caching, since the server follows the directions provided by the client. A new response is returned for every request, even if the server has a fresh cached response. With output caching the client doesn't override the caching behavior that you configure on the server.

* The cache storage medium is extensible.

Memory is used by default. Response caching is limited to memory.

* You can programmatically invalidate selected cache entries.

Response caching's dependence on HTTP headers leaves you with few options for invalidating cache entries.

* Resource locking mitigates the risk of cache stampede and thundering herd.

*Cache stampede* happens when a frequently used cache entry is revoked, and too many requests try to repopulate the same cache entry at the same time. *Thundering herd* is similar: a burst of requests for the same response that isn't already in a cache entry. Resource locking ensures that all requests for a given response wait for the the first request to populate the cache. Response caching doesn't have a resource locking feature.

* Cache revalidation minimizes bandwidth usage.

*Cache revalidation* means the server can return a `304 Not Modified` HTTP status code instead of a cached response body. This status code informs the client that the response to the request is unchanged from what was previously received. Response caching doesn't do cache revalidation.
2 changes: 2 additions & 0 deletions aspnetcore/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,8 @@ items:
uid: performance/caching/memory
- name: Distributed caching
uid: performance/caching/distributed
- name: Output caching
uid: performance/caching/output
- name: Response caching
uid: performance/caching/response
- name: Response caching middleware
Expand Down