-
Notifications
You must be signed in to change notification settings - Fork 24.8k
Output caching documentation #27398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Output caching documentation #27398
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
3ef88bf
draft
tdykstra 2fc2254
add to toc
tdykstra 92921b7
fix link
tdykstra a621df8
remove link
tdykstra 2be68ad
fix snippet links
tdykstra 3d9dcc3
proofread updates
tdykstra 676fcb6
Apply suggestions from code review
tdykstra bf885af
Apply suggestions from code review
tdykstra 4c4628a
fix list spacing
tdykstra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: | ||
|
|
||
| :::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. | ||
|
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": | ||
|
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: | ||
|
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
17
aspnetcore/performance/caching/output/samples/7.x/Gravatar.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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>"); | ||
| } | ||
| } |
14 changes: 14 additions & 0 deletions
14
aspnetcore/performance/caching/output/samples/7.x/OCMinimal.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
126
aspnetcore/performance/caching/output/samples/7.x/Program.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | ||
| } | ||
| } |
8 changes: 8 additions & 0 deletions
8
aspnetcore/performance/caching/output/samples/7.x/appsettings.Development.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning" | ||
| } | ||
| } | ||
| } |
9 changes: 9 additions & 0 deletions
9
aspnetcore/performance/caching/output/samples/7.x/appsettings.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning" | ||
| } | ||
| }, | ||
| "AllowedHosts": "*" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.