Middleware für die Ausgabezwischenspeicherung in ASP.NET Core
Von Tom Dykstra
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
In diesem Artikel wird erläutert, wie die Middleware für die Ausgabezwischenspeicherung in einer ASP.NET Core-App konfiguriert wird. Eine Einführung in die Ausgabezwischenspeicherung finden Sie unter Ausgabezwischenspeicherung.
Die Middleware für die Ausgabezwischenspeicherung kann in allen Arten von ASP.NET Core-Apps verwendet werden: Minimal-API, Web-API mit Controllern, MVC und Razor Pages. Codebeispiele werden für Minimal-APIs und controllerbasierte APIs bereitgestellt. Die controllerbasierten API-Beispiele zeigen, wie Attribute zum Konfigurieren der Zwischenspeicherung verwendet werden. Diese Attribute können auch in MVC- und Razor Pages-Apps verwendet werden.
Die Codebeispiele beziehen sich auf eine Gravatar-Klasse, die ein Bild generiert und ein Datum und eine Uhrzeit der Generierung bereitstellt. Die Klasse wird definiert und nur in der Beispiel-App verwendet. Ihr Zweck besteht darin, zu erkennen, wann die zwischengespeicherte Ausgabe verwendet wird. Weitere Informationen finden Sie unter Herunterladen eines Beispiels und Präprozessordirektiven im Beispielcode.
Hinzufügen der Middleware zur App
Fügen Sie die Middleware für die Ausgabezwischenspeicherung zur Dienstsammlung hinzu, indem Sie AddOutputCache aufrufen.
Fügen Sie die Middleware zur Anforderungsverarbeitungspipeline hinzu, indem Sie UseOutputCache aufrufen.
Zum Beispiel:
builder.Services.AddOutputCache();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseOutputCache();
app.UseAuthorization();
Durch Aufrufen von AddOutputCache
und UseOutputCache
wird nicht das Zwischenspeicherungsverhalten gestartet, sondern das Zwischenspeichern verfügbar gemacht. Damit die App-Cacheantworten ausgeführt werden können, muss die Zwischenspeicherung wie in den folgenden Abschnitten dargestellt konfiguriert sein.
Hinweis
- In Apps, die CORS-Middleware verwenden, muss
UseOutputCache
nach UseCors aufgerufen werden. - In Razor Pages-Apps und Apps mit Controllern muss
UseOutputCache
nachUseRouting
aufgerufen werden.
Konfigurieren eines Endpunkts oder einer Seite
Bei minimalen API-Apps konfigurieren Sie einen Endpunkt für die Zwischenspeicherung, indem Sie CacheOutput
aufrufen oder das [OutputCache]
-Attribut anwenden, wie in den folgenden Beispielen gezeigt:
app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) =>
Gravatar.WriteGravatar(context));
Bei Apps mit Controllern wenden Sie das [OutputCache]
-Attribut wie hier gezeigt auf die Aktionsmethode an:
[ApiController]
[Route("/[controller]")]
[OutputCache]
public class CachedController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Bei Razor Pages-Apps wenden Sie das Attribut auf die Razor-Seitenklasse an.
Konfigurieren mehrerer Endpunkte oder Seiten
Erstellen Sie Richtlinien beim Aufrufen von AddOutputCache
, um eine Zwischenspeicherungskonfiguration anzugeben, die für mehrere Endpunkte gilt. Eine Richtlinie kann für bestimmte Endpunkte ausgewählt werden, während eine Basisrichtlinie eine Standardzwischenspeicherungskonfiguration für eine Sammlung von Endpunkten bereitstellt.
Der folgende hervorgehobene Code konfiguriert die Zwischenspeicherung für alle Endpunkte der App mit einer Ablaufzeit von 10 Sekunden. Wenn keine Ablaufzeit angegeben ist, wird standardmäßig eine Minute festgelegt.
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)));
});
Der folgende hervorgehobene Code erstellt zwei Richtlinien, die jeweils eine andere Ablaufzeit angeben. Ausgewählte Endpunkte können die Ablaufzeit von 20 Sekunden verwenden, und für andere Endpunkte kann die Ablaufzeit von 30 Sekunden gelten.
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)));
});
Sie können eine Richtlinie für einen Endpunkt auswählen, wenn Sie die CacheOutput
-Methode aufrufen oder das [OutputCache]
-Attribut verwenden.
In einer Minimal-API-App konfiguriert der folgende Code einen Endpunkt mit einem Ablauf von 20 Sekunden und einen mit einem Ablauf von 30 Sekunden:
app.MapGet("/20", Gravatar.WriteGravatar).CacheOutput("Expire20");
app.MapGet("/30", [OutputCache(PolicyName = "Expire30")] (context) =>
Gravatar.WriteGravatar(context));
Bei Apps mit Controllern wenden Sie das [OutputCache]
-Attribut auf die Aktionsmethode an, um eine Richtlinie auszuwählen:
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "Expire20")]
public class Expire20Controller : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Bei Razor Pages-Apps wenden Sie das Attribut auf die Razor-Seitenklasse an.
Standardrichtlinie für die Ausgabezwischenspeicherung
Standardmäßig folgt die Ausgabezwischenspeicherung den folgenden Regeln:
- Es werden nur HTTP 200-Antworten zwischengespeichert.
- Es werden nur HTTP GET- oder HEAD-Anforderungen zwischengespeichert.
- Antworten, die Cookies setzen, werden nicht zwischengespeichert.
- Antworten auf authentifizierte Anforderungen werden nicht zwischengespeichert.
Der folgende Code wendet alle Standardregeln für die Zwischenspeicherung auf alle Endpunkte einer App an:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache());
});
Überschreiben der Standardrichtlinie
Der folgende Code zeigt, wie die Standardregeln überschrieben werden. Die hervorgehobenen Zeilen im folgenden benutzerdefinierten Richtliniencode ermöglichen die Zwischenspeicherung für HTTP POST-Methoden und HTTP 301-Antworten:
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.Primitives;
namespace OCMinimal;
public sealed class MyCustomPolicy : IOutputCachePolicy
{
public static readonly MyCustomPolicy Instance = new();
private MyCustomPolicy()
{
}
ValueTask IOutputCachePolicy.CacheRequestAsync(
OutputCacheContext context,
CancellationToken cancellationToken)
{
var attemptOutputCaching = AttemptOutputCaching(context);
context.EnableOutputCaching = true;
context.AllowCacheLookup = attemptOutputCaching;
context.AllowCacheStorage = attemptOutputCaching;
context.AllowLocking = true;
// Vary by any query by default
context.CacheVaryByRules.QueryKeys = "*";
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeFromCacheAsync
(OutputCacheContext context, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeResponseAsync
(OutputCacheContext context, CancellationToken cancellationToken)
{
var response = context.HttpContext.Response;
// Verify existence of cookie headers
if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
// Check response code
if (response.StatusCode != StatusCodes.Status200OK &&
response.StatusCode != StatusCodes.Status301MovedPermanently)
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
return ValueTask.CompletedTask;
}
private static bool AttemptOutputCaching(OutputCacheContext context)
{
// Check if the current request fulfills the requirements
// to be cached
var request = context.HttpContext.Request;
// Verify the method
if (!HttpMethods.IsGet(request.Method) &&
!HttpMethods.IsHead(request.Method) &&
!HttpMethods.IsPost(request.Method))
{
return false;
}
// Verify existence of authorization headers
if (!StringValues.IsNullOrEmpty(request.Headers.Authorization) ||
request.HttpContext.User?.Identity?.IsAuthenticated == true)
{
return false;
}
return true;
}
}
Um diese benutzerdefinierte Richtlinie zu verwenden, erstellen Sie eine benannte Richtlinie:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", MyCustomPolicy.Instance);
});
Wählen Sie zudem die benannte Richtlinie für einen Endpunkt aus. Der folgende Code wählt die benutzerdefinierte Richtlinie für einen Endpunkt in einer Minimal-API-App aus:
app.MapPost("/cachedpost", Gravatar.WriteGravatar)
.CacheOutput("CachePost");
Der folgende Code funktioniert genauso für eine Controlleraktion:
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "CachePost")]
public class PostController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Alternative Außerkraftsetzung von Standardrichtlinien
Alternativ können Sie die Abhängigkeitsinjektion (Dependency Injection, DI) zum Initialisieren einer Instanz verwenden, indem Sie folgende Änderungen an der benutzerdefinierten Richtlinienklasse vornehmen:
- Verwenden Sie einen öffentlichen Konstruktor anstelle eines privaten Konstruktors.
- Entfernen Sie die
Instance
-Eigenschaft in der benutzerdefinierten Richtlinienklasse.
Beispiel:
public sealed class MyCustomPolicy2 : IOutputCachePolicy
{
public MyCustomPolicy2()
{
}
Der Rest der Klasse ist identisch mit der zuvor gezeigten. Fügen Sie die benutzerdefinierte Richtlinie wie im folgenden Beispiel gezeigt hinzu:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", builder =>
builder.AddPolicy<MyCustomPolicy2>(), true);
});
Der vorangehende Code verwendet DI, um die Instanz der benutzerdefinierten Richtlinienklasse zu erstellen. Alle öffentlichen Argumente im Konstruktor werden aufgelöst.
Wenn Sie eine benutzerdefinierte Richtlinie als Basisrichtlinie verwenden, rufen Sie nicht OutputCache()
(ohne Argumente) auf oder verwenden das [OutputCache]
-Attribut für einen Endpunkt, für den die Basisrichtlinie gelten soll. Durch Aufrufen von OutputCache()
oder Verwenden des Attributs wird dem Endpunkt die Standardrichtlinie hinzugefügt.
Angeben des Cacheschlüssels
Standardmäßig ist jeder Teil der URL als Schlüssel für einen Cacheeintrag enthalten, d. h. Schema, Host, Port, Pfad und Abfragezeichenfolge. Möglicherweise möchten Sie den Cacheschlüssel jedoch explizit steuern. Angenommen, Sie verfügen über einen Endpunkt, der nur für jeden eindeutigen Wert der culture
-Abfragezeichenfolge eine eindeutige Antwort zurückgibt. Variationen in anderen Teilen der URL, z. B. andere Abfragezeichenfolgen, sollten nicht zu unterschiedlichen Cacheeinträgen führen. Sie können solche Regeln in einer Richtlinie angeben, wie im folgenden hervorgehobenen Code gezeigt:
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));
});
Anschließend können Sie die VaryByQuery
-Richtlinie für einen Endpunkt auswählen. In einer Minimal-API-App wählt der folgende Code die VaryByQuery
-Richtlinie für einen Endpunkt aus, der für jeden eindeutigen Wert der culture
-Abfragezeichenfolge nur eine eindeutige Antwort zurückgibt:
app.MapGet("/query", Gravatar.WriteGravatar).CacheOutput("Query");
Der folgende Code funktioniert genauso für eine Controlleraktion:
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "Query")]
public class QueryController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Im Folgenden sind einige Optionen zum Steuern des Cacheschlüssels aufgeführt:
SetVaryByQuery: Geben Sie einen oder mehrere Abfragezeichenfolgennamen an, die dem Cacheschlüssel hinzugefügt werden sollen.
SetVaryByHeader: Geben Sie einen oder mehrere HTTP-Header an, die dem Cacheschlüssel hinzugefügt werden sollen.
VaryByValue: Geben Sie einen Wert an, der dem Cacheschlüssel hinzugefügt werden soll. Im folgenden Beispiel wird ein Wert verwendet, der angibt, ob die aktuelle Serverzeit in Sekunden ungerade oder gerade ist. Eine neue Antwort wird nur generiert, wenn die Anzahl der Sekunden von ungerade zu gerade oder von gerade zu ungerade wechselt.
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)); options.AddPolicy("VaryByValue", builder => builder.VaryByValue((context) => new KeyValuePair<string, string>( "time", (DateTime.Now.Second % 2) .ToString(CultureInfo.InvariantCulture)))); });
Verwenden Sie OutputCacheOptions.UseCaseSensitivePaths, um anzugeben, dass für den Pfadteil des Schlüssels die Groß-/Kleinschreibung relevant ist. In der Standardeinstellung wird die Groß-/Kleinschreibung nicht berücksichtigt.
Weitere Optionen finden Sie in der OutputCachePolicyBuilder-Klasse.
Erneute Cacheüberprüfung
Eine erneute Cacheüberprüfung bedeutet, dass der Server anstelle des gesamten Antworttexts einen 304 Not Modified
-HTTP-Statuscode zurückgeben kann. Dieser Statuscode informiert den Client darüber, dass die Antwort auf die Anforderung mit der zuvor vom Client empfangenen Antwort identisch ist.
Der folgende Code veranschaulicht die Verwendung eines Etag
-Headers zum Aktivieren der erneuten Cacheüberprüfung. Wenn der Client einen If-None-Match
-Header mit dem ETag-Wert einer früheren Antwort sendet und der Cacheeintrag neu ist, gibt der Server 304 Nicht geändert anstelle der vollständigen Antwort zurück. So legen Sie den ETag-Wert in einer Richtlinie in einer Minimal-API-App fest:
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(context);
}).CacheOutput();
So legen Sie den ETag-Wert in einer controllerbasierten API fest:
[ApiController]
[Route("/[controller]")]
[OutputCache]
public class EtagController : ControllerBase
{
public async Task GetAsync()
{
var etag = $"\"{Guid.NewGuid():n}\"";
HttpContext.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(HttpContext);
}
}
Eine weitere Möglichkeit zur erneuten Cacheüberprüfung besteht darin, das Erstellungsdatum des Cacheeintrags im Vergleich zum Datum der Clientanforderung zu überprüfen. Wenn der Anforderungsheader If-Modified-Since
bereitgestellt wird, gibt die Ausgabezwischenspeicherung 304 zurück, wenn der zwischengespeicherte Eintrag älter und nicht abgelaufen ist.
Die erneute Cacheüberprüfung erfolgt automatisch als Reaktion auf diese vom Client gesendeten Header. Abgesehen von der Aktivierung der Ausgabezwischenspeicherung ist keine spezielle Konfiguration auf dem Server erforderlich, um dieses Verhalten zu aktivieren.
Verwenden von Tags zum Entfernen von Cacheeinträgen
Sie können Tags verwenden, um eine Gruppe von Endpunkten zu identifizieren und alle Cacheeinträge für die Gruppe zu entfernen. Der folgende Minimal-API-Code erstellt beispielsweise ein Endpunktpaar, deren URLs mit „blog“ beginnen, und taggt sie mit „tag-blog“:
app.MapGet("/blog", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
app.MapGet("/blog/post/{id}", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
Der folgende Code zeigt, wie einem Endpunkt in einer controllerbasierten API Tags zugewiesen werden:
[ApiController]
[Route("/[controller]")]
[OutputCache(Tags = new[] { "tag-blog", "tag-all" })]
public class TagEndpointController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Eine alternative Möglichkeit zum Zuweisen von Tags für Endpunkte mit Routen, die mit blog
beginnen, besteht darin, eine Basisrichtlinie zu definieren, die für alle Endpunkte mit dieser Route gilt. Dies wird im folgenden Code veranschaulicht:
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));
});
Eine weitere Alternative für Minimal-API-Apps besteht darin, MapGroup
aufzurufen:
var blog = app.MapGroup("blog")
.CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);
In den obigen Tagzuweisungsbeispielen werden beide Endpunkte durch das tag-blog
-Tag identifiziert. Anschließend können Sie die Cacheeinträge für diese Endpunkte mit einer einzigen Anweisung entfernen, die auf dieses Tag verweist:
app.MapPost("/purge/{tag}", async (IOutputCacheStore cache, string tag) =>
{
await cache.EvictByTagAsync(tag, default);
});
Mit diesem Code werden Cacheeinträge für diese Endpunkte durch eine HTTP POST-Anforderung entfernt, die an https://localhost:<port>/purge/tag-blog
gesendet wird.
Möglicherweise benötigen Sie eine Methode zum Entfernen aller Cacheeinträge für alle Endpunkte. Erstellen Sie hierzu eine Basisrichtlinie für alle Endpunkte, wie im folgenden Code gezeigt:
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));
});
Mit dieser Basisrichtlinie können Sie das Tag „tag-all“ verwenden, um alle Einträge im Cache zu entfernen.
Deaktivieren der Ressourcensperre
Standardmäßig ist die Ressourcensperre aktiviert, um das Risiko von Cacheüberlastung (Stampede) und Thundering Herd zu verringern. Weitere Informationen finden Sie unter Ausgabezwischenspeicherung.
Um die Ressourcensperre zu deaktivieren, rufen Sie beim Erstellen einer Richtlinie SetLocking(false) auf, wie im folgenden Beispiel gezeigt:
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));
});
Im folgenden Beispiel wird die Richtlinie ohne Sperrung für einen Endpunkt ausgewählt in einer Minimal-API-App ausgewählt:
app.MapGet("/nolock", Gravatar.WriteGravatar)
.CacheOutput("NoLock");
Verwenden Sie in einer controllerbasierten API das Attribut, um die Richtlinie auszuwählen:
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "NoLock")]
public class NoLockController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Grenzwerte
Mit den folgenden Eigenschaften von OutputCacheOptions können Sie Grenzwerte konfigurieren, die für alle Endpunkte gelten:
- SizeLimit: Maximale Größe des Cachespeichers. Wenn dieser Grenzwert erreicht ist, werden keine neuen Antworten zwischengespeichert, bis ältere Einträge entfernt werden. Der Standardwert ist 100 MB.
- MaximumBodySize - Wenn der Antworttext diesen Grenzwert überschreitet, wird er nicht zwischengespeichert. Der Standardwert ist 64 MB.
- DefaultExpirationTimeSpan: Die Dauer der Ablaufzeit, die gilt, wenn keine Richtlinienangabe vorliegt. Der Standardwert ist 60 Sekunden.
Cachespeicher
IOutputCacheStore wird für die Speicherung verwendet. Diese Angabe wird standardmäßig mit MemoryCache verwendet. Zwischengespeicherte Antworten werden „In-Process“ gespeichert, sodass jeder Server über einen separaten Cache verfügt, der bei jedem Neustart des Serverprozesses verloren geht.
Redis Cache
Eine Alternative ist die Verwendung des Redis-Caches. Der Redis-Cache bietet Konsistenz zwischen Serverknoten über einen freigegebenen Cache, der einzelne Serverprozesse überdauert. So verwenden Sie Redis für die Ausgabezwischenspeicherung:
Installieren Sie das NuGet-Paket Microsoft.AspNetCore.OutputCaching.StackExchangeRedis.
Rufen Sie
builder.Services.AddStackExchangeRedisOutputCache
(nichtAddStackExchangeRedisCache
) auf und stellen Sie eine Verbindungszeichenfolge bereit, die auf einen Redis-Server verweist.Beispiel:
builder.Services.AddStackExchangeRedisOutputCache(options => { options.Configuration = builder.Configuration.GetConnectionString("MyRedisConStr"); options.InstanceName = "SampleInstance"; }); builder.Services.AddOutputCache(options => { options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(10))); });
options.Configuration
– Eine Verbindungszeichenfolge mit einem lokalen Redis-Server oder einem gehosteten Angebot wie Azure Cache für Redis. Zum Beispiel<instance_name>.redis.cache.windows.net:6380,password=<password>,ssl=True,abortConnect=False
für Azure Cache für Redis.options.InstanceName
- Optional, gibt eine logische Partition für den Cache an.
Die Konfigurationsoptionen sind identisch mit den Redis-basierten Optionen für verteilte Zwischenspeicherung.
IDistributedCache
nicht empfohlen
Es wird nicht empfohlen IDistributedCache zur Ausgabezwischenspeicherung zu verwenden. IDistributedCache
verfügt nicht über atomische Features, die für das Tagging erforderlich sind. Es wird empfohlen, die integrierte Unterstützung für Redis zu verwenden oder benutzerdefinierte IOutputCacheStore-Implementierungen zu erstellen, indem Sie direkte Abhängigkeiten vom zugrunde liegenden Speichermechanismus verwenden.
Siehe auch
In diesem Artikel wird erläutert, wie die Middleware für die Ausgabezwischenspeicherung in einer ASP.NET Core-App konfiguriert wird. Eine Einführung in die Ausgabezwischenspeicherung finden Sie unter Ausgabezwischenspeicherung.
Die Middleware für die Ausgabezwischenspeicherung kann in allen Arten von ASP.NET Core-Apps verwendet werden: Minimal-API, Web-API mit Controllern, MVC und Razor Pages. Die Beispiel-App ist eine Minimal-API, aber die darin demonstrierten Zwischenspeicherungsfeatures werden auch in den anderen App-Typen unterstützt.
Hinzufügen der Middleware zur App
Fügen Sie die Middleware für die Ausgabezwischenspeicherung zur Dienstsammlung hinzu, indem Sie AddOutputCache aufrufen.
Fügen Sie die Middleware zur Anforderungsverarbeitungspipeline hinzu, indem Sie UseOutputCache aufrufen.
Hinweis
- In Apps, die CORS-Middleware verwenden, muss
UseOutputCache
nach UseCors aufgerufen werden. - In Razor Pages-Apps und Apps mit Controllern muss
UseOutputCache
nachUseRouting
aufgerufen werden. - Durch Aufrufen von
AddOutputCache
undUseOutputCache
wird nicht das Zwischenspeicherungsverhalten gestartet, sondern das Zwischenspeichern verfügbar gemacht. Das Zwischenspeichern von Antwortdaten muss wie in den folgenden Abschnitten gezeigt konfiguriert werden.
Konfigurieren eines Endpunkts oder einer Seite
Bei minimalen API-Apps konfigurieren Sie einen Endpunkt für die Zwischenspeicherung, indem Sie CacheOutput
aufrufen oder das [OutputCache]
-Attribut anwenden, wie in den folgenden Beispielen gezeigt:
app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) =>
Gravatar.WriteGravatar(context));
Bei Apps mit Controllern wenden Sie das [OutputCache]
-Attribut auf die Aktionsmethode an. Bei Razor Pages-Apps wenden Sie das Attribut auf die Razor-Seitenklasse an.
Konfigurieren mehrerer Endpunkte oder Seiten
Erstellen Sie Richtlinien beim Aufrufen von AddOutputCache
, um eine Zwischenspeicherungskonfiguration anzugeben, die für mehrere Endpunkte gilt. Eine Richtlinie kann für bestimmte Endpunkte ausgewählt werden, während eine Basisrichtlinie eine Standardzwischenspeicherungskonfiguration für eine Sammlung von Endpunkten bereitstellt.
Der folgende hervorgehobene Code konfiguriert die Zwischenspeicherung für alle Endpunkte der App mit einer Ablaufzeit von 10 Sekunden. Wenn keine Ablaufzeit angegeben ist, wird standardmäßig eine Minute festgelegt.
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)));
});
Der folgende hervorgehobene Code erstellt zwei Richtlinien, die jeweils eine andere Ablaufzeit angeben. Ausgewählte Endpunkte können die Ablaufzeit von 20 Sekunden verwenden, und für andere Endpunkte kann die Ablaufzeit von 30 Sekunden gelten.
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)));
});
Sie können eine Richtlinie für einen Endpunkt auswählen, wenn Sie die CacheOutput
-Methode aufrufen oder das [OutputCache]
-Attribut verwenden:
app.MapGet("/20", Gravatar.WriteGravatar).CacheOutput("Expire20");
app.MapGet("/30", [OutputCache(PolicyName = "Expire30")] (context) =>
Gravatar.WriteGravatar(context));
Bei Apps mit Controllern wenden Sie das [OutputCache]
-Attribut auf die Aktionsmethode an. Bei Razor Pages-Apps wenden Sie das Attribut auf die Razor-Seitenklasse an.
Standardrichtlinie für die Ausgabezwischenspeicherung
Standardmäßig folgt die Ausgabezwischenspeicherung den folgenden Regeln:
- Es werden nur HTTP 200-Antworten zwischengespeichert.
- Es werden nur HTTP GET- oder HEAD-Anforderungen zwischengespeichert.
- Antworten, die Cookies setzen, werden nicht zwischengespeichert.
- Antworten auf authentifizierte Anforderungen werden nicht zwischengespeichert.
Der folgende Code wendet alle Standardregeln für die Zwischenspeicherung auf alle Endpunkte einer App an:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache());
});
Überschreiben der Standardrichtlinie
Der folgende Code zeigt, wie die Standardregeln überschrieben werden. Die hervorgehobenen Zeilen im folgenden benutzerdefinierten Richtliniencode ermöglichen die Zwischenspeicherung für HTTP POST-Methoden und HTTP 301-Antworten:
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.Primitives;
namespace OCMinimal;
public sealed class MyCustomPolicy : IOutputCachePolicy
{
public static readonly MyCustomPolicy Instance = new();
private MyCustomPolicy()
{
}
ValueTask IOutputCachePolicy.CacheRequestAsync(
OutputCacheContext context,
CancellationToken cancellationToken)
{
var attemptOutputCaching = AttemptOutputCaching(context);
context.EnableOutputCaching = true;
context.AllowCacheLookup = attemptOutputCaching;
context.AllowCacheStorage = attemptOutputCaching;
context.AllowLocking = true;
// Vary by any query by default
context.CacheVaryByRules.QueryKeys = "*";
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeFromCacheAsync
(OutputCacheContext context, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeResponseAsync
(OutputCacheContext context, CancellationToken cancellationToken)
{
var response = context.HttpContext.Response;
// Verify existence of cookie headers
if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
// Check response code
if (response.StatusCode != StatusCodes.Status200OK &&
response.StatusCode != StatusCodes.Status301MovedPermanently)
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
return ValueTask.CompletedTask;
}
private static bool AttemptOutputCaching(OutputCacheContext context)
{
// Check if the current request fulfills the requirements
// to be cached
var request = context.HttpContext.Request;
// Verify the method
if (!HttpMethods.IsGet(request.Method) &&
!HttpMethods.IsHead(request.Method) &&
!HttpMethods.IsPost(request.Method))
{
return false;
}
// Verify existence of authorization headers
if (!StringValues.IsNullOrEmpty(request.Headers.Authorization) ||
request.HttpContext.User?.Identity?.IsAuthenticated == true)
{
return false;
}
return true;
}
}
Um diese benutzerdefinierte Richtlinie zu verwenden, erstellen Sie eine benannte Richtlinie:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", MyCustomPolicy.Instance);
});
Wählen Sie dann die benannte Richtlinie für einen Endpunkt aus:
app.MapPost("/cachedpost", Gravatar.WriteGravatar)
.CacheOutput("CachePost");
Alternative Außerkraftsetzung von Standardrichtlinien
Alternativ können Sie die Abhängigkeitsinjektion (Dependency Injection, DI) zum Initialisieren einer Instanz verwenden, indem Sie folgende Änderungen an der benutzerdefinierten Richtlinienklasse vornehmen:
- Verwenden Sie einen öffentlichen Konstruktor anstelle eines privaten Konstruktors.
- Entfernen Sie die
Instance
-Eigenschaft in der benutzerdefinierten Richtlinienklasse.
Beispiel:
public sealed class MyCustomPolicy2 : IOutputCachePolicy
{
public MyCustomPolicy2()
{
}
Der Rest der Klasse ist identisch mit der zuvor gezeigten. Fügen Sie die benutzerdefinierte Richtlinie wie im folgenden Beispiel gezeigt hinzu:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", builder =>
builder.AddPolicy<MyCustomPolicy2>(), true);
});
Der vorangehende Code verwendet DI, um die Instanz der benutzerdefinierten Richtlinienklasse zu erstellen. Alle öffentlichen Argumente im Konstruktor werden aufgelöst.
Wenn Sie eine benutzerdefinierte Richtlinie als Basisrichtlinie verwenden, rufen Sie OutputCache()
(ohne Argumente) nicht für einen Endpunkt auf, für den die Basisrichtlinie gelten soll. Durch Aufrufen von OutputCache()
wird dem Endpunkt die Standardrichtlinie hinzugefügt.
Angeben des Cacheschlüssels
Standardmäßig ist jeder Teil der URL als Schlüssel für einen Cacheeintrag enthalten, d. h. Schema, Host, Port, Pfad und Abfragezeichenfolge. Möglicherweise möchten Sie den Cacheschlüssel jedoch explizit steuern. Angenommen, Sie verfügen über einen Endpunkt, der nur für jeden eindeutigen Wert der culture
-Abfragezeichenfolge eine eindeutige Antwort zurückgibt. Variationen in anderen Teilen der URL, z. B. andere Abfragezeichenfolgen, sollten nicht zu unterschiedlichen Cacheeinträgen führen. Sie können solche Regeln in einer Richtlinie angeben, wie im folgenden hervorgehobenen Code gezeigt:
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));
});
Anschließend können Sie die VaryByQuery
-Richtlinie für einen Endpunkt auswählen:
app.MapGet("/query", Gravatar.WriteGravatar).CacheOutput("Query");
Im Folgenden sind einige Optionen zum Steuern des Cacheschlüssels aufgeführt:
SetVaryByQuery: Geben Sie einen oder mehrere Abfragezeichenfolgennamen an, die dem Cacheschlüssel hinzugefügt werden sollen.
SetVaryByHeader: Geben Sie einen oder mehrere HTTP-Header an, die dem Cacheschlüssel hinzugefügt werden sollen.
VaryByValue: Geben Sie einen Wert an, der dem Cacheschlüssel hinzugefügt werden soll. Im folgenden Beispiel wird ein Wert verwendet, der angibt, ob die aktuelle Serverzeit in Sekunden ungerade oder gerade ist. Eine neue Antwort wird nur generiert, wenn die Anzahl der Sekunden von ungerade zu gerade oder von gerade zu ungerade wechselt.
app.MapGet("/varybyvalue", Gravatar.WriteGravatar) .CacheOutput(c => c.VaryByValue((context) => new KeyValuePair<string, string>( "time", (DateTime.Now.Second % 2) .ToString(CultureInfo.InvariantCulture))));
Verwenden Sie OutputCacheOptions.UseCaseSensitivePaths, um anzugeben, dass für den Pfadteil des Schlüssels die Groß-/Kleinschreibung relevant ist. In der Standardeinstellung wird die Groß-/Kleinschreibung nicht berücksichtigt.
Weitere Optionen finden Sie in der OutputCachePolicyBuilder-Klasse.
Erneute Cacheüberprüfung
Eine erneute Cacheüberprüfung bedeutet, dass der Server anstelle des gesamten Antworttexts einen 304 Not Modified
-HTTP-Statuscode zurückgeben kann. Dieser Statuscode informiert den Client darüber, dass die Antwort auf die Anforderung mit der zuvor vom Client empfangenen Antwort identisch ist.
Der folgende Code veranschaulicht die Verwendung eines Etag
-Headers zum Aktivieren der erneuten Cacheüberprüfung. Wenn der Client einen If-None-Match
-Header mit dem ETag-Wert einer früheren Antwort sendet und der Cacheeintrag neu ist, gibt der Server 304 Nicht geändert anstelle der vollständigen Antwort zurück:
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(context);
}).CacheOutput();
Eine weitere Möglichkeit zur erneuten Cacheüberprüfung besteht darin, das Erstellungsdatum des Cacheeintrags im Vergleich zum Datum der Clientanforderung zu überprüfen. Wenn der Anforderungsheader If-Modified-Since
bereitgestellt wird, gibt die Ausgabezwischenspeicherung 304 zurück, wenn der zwischengespeicherte Eintrag älter und nicht abgelaufen ist.
Die erneute Cacheüberprüfung erfolgt automatisch als Reaktion auf diese vom Client gesendeten Header. Abgesehen von der Aktivierung der Ausgabezwischenspeicherung ist keine spezielle Konfiguration auf dem Server erforderlich, um dieses Verhalten zu aktivieren.
Verwenden von Tags zum Entfernen von Cacheeinträgen
Sie können Tags verwenden, um eine Gruppe von Endpunkten zu identifizieren und alle Cacheeinträge für die Gruppe zu entfernen. Der folgende Code erstellt beispielsweise ein Paar von Endpunkten, deren URLs mit „blog“ beginnen, und markiert sie mit „tag-blog“:
app.MapGet("/blog", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
app.MapGet("/blog/post/{id}", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
Eine alternative Möglichkeit zum Zuweisen von Tags für dasselbe Endpunktpaar besteht darin, eine Basisrichtlinie zu definieren, die für Endpunkte gilt, die mit blog
beginnen:
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));
});
Eine weitere Alternative besteht im Aufrufen von MapGroup
:
var blog = app.MapGroup("blog")
.CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);
In den obigen Tagzuweisungsbeispielen werden beide Endpunkte durch das tag-blog
-Tag identifiziert. Anschließend können Sie die Cacheeinträge für diese Endpunkte mit einer einzigen Anweisung entfernen, die auf dieses Tag verweist:
app.MapPost("/purge/{tag}", async (IOutputCacheStore cache, string tag) =>
{
await cache.EvictByTagAsync(tag, default);
});
Mit diesem Code werden Cacheeinträge für diese Endpunkte durch eine HTTP POST-Anforderung entfernt, die an https://localhost:<port>/purge/tag-blog
gesendet wird.
Möglicherweise benötigen Sie eine Methode zum Entfernen aller Cacheeinträge für alle Endpunkte. Erstellen Sie hierzu eine Basisrichtlinie für alle Endpunkte, wie im folgenden Code gezeigt:
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));
});
Mit dieser Basisrichtlinie können Sie das Tag „tag-all“ verwenden, um alle Einträge im Cache zu entfernen.
Deaktivieren der Ressourcensperre
Standardmäßig ist die Ressourcensperre aktiviert, um das Risiko von Cacheüberlastung (Stampede) und Thundering Herd zu verringern. Weitere Informationen finden Sie unter Ausgabezwischenspeicherung.
Um die Ressourcensperre zu deaktivieren, rufen Sie beim Erstellen einer Richtlinie SetLocking(false) auf, wie im folgenden Beispiel gezeigt:
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));
});
Im folgenden Beispiel wird die Richtlinie ohne Sperren für einen Endpunkt ausgewählt:
app.MapGet("/nolock", Gravatar.WriteGravatar)
.CacheOutput("NoLock");
Grenzwerte
Mit den folgenden Eigenschaften von OutputCacheOptions können Sie Grenzwerte konfigurieren, die für alle Endpunkte gelten:
- SizeLimit: Maximale Größe des Cachespeichers. Wenn dieser Grenzwert erreicht ist, werden keine neuen Antworten zwischengespeichert, bis ältere Einträge entfernt werden. Der Standardwert ist 100 MB.
- MaximumBodySize: Wenn der Antworttext diesen Grenzwert überschreitet, wird er nicht zwischengespeichert. Der Standardwert ist 64 MB.
- DefaultExpirationTimeSpan: Die Dauer der Ablaufzeit, die gilt, wenn keine Richtlinienangabe vorliegt. Der Standardwert ist 60 Sekunden.
Cachespeicher
IOutputCacheStore wird für die Speicherung verwendet. Diese Angabe wird standardmäßig mit MemoryCache verwendet. Es wird nicht empfohlen IDistributedCache zur Ausgabezwischenspeicherung zu verwenden. IDistributedCache
verfügt nicht über atomische Features, die für das Tagging erforderlich sind. Es wird empfohlen, benutzerdefinierte IOutputCacheStore-Implementierungen zu erstellen, indem Sie direkte Abhängigkeiten vom zugrunde liegenden Speichermechanismus (wie z. B. Redis) verwenden. Oder verwenden Sie die integrierte Unterstützung für Redis Cache in .NET 8..