Bagikan melalui


Middleware penembolokan output di ASP.NET Core

Oleh Tom Dykstra

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Artikel ini menjelaskan cara mengonfigurasi middleware penembolokan output di aplikasi ASP.NET Core. Untuk pengenalan penembolokan output, lihat Penembolokan output.

Middleware penembolokan output dapat digunakan di semua jenis aplikasi ASP.NET Core: API Minimal, API Web dengan pengontrol, MVC, dan Razor Pages. Aplikasi sampel adalah API Minimal, tetapi setiap fitur penembolokan yang diilustrasikannya juga didukung di jenis aplikasi lain.

Menambahkan middleware ke aplikasi

Tambahkan middleware penembolokan output ke kumpulan layanan dengan memanggil AddOutputCache.

Tambahkan middleware ke alur pemrosesan permintaan dengan memanggil UseOutputCache.

Catatan

  • Dalam aplikasi yang menggunakan middleware CORS, UseOutputCache harus dipanggil setelah UseCors.
  • Di Razor aplikasi dan aplikasi Pages dengan pengontrol, UseOutputCache harus dipanggil setelah UseRouting.
  • Memanggil AddOutputCachedan UseOutputCache tidak memulai perilaku penembolokan, membuat penembolokan tersedia. Data respons penembolokan harus dikonfigurasi seperti yang ditunjukkan di bagian berikut.

Mengonfigurasi satu titik akhir atau halaman

Untuk aplikasi API minimal, konfigurasikan titik akhir untuk melakukan penembolokan dengan memanggil CacheOutput, atau dengan menerapkan [OutputCache] atribut , seperti yang ditunjukkan dalam contoh berikut:

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

Untuk aplikasi dengan pengontrol, terapkan [OutputCache] atribut ke metode tindakan. Untuk Razor aplikasi Pages, terapkan atribut ke Razor kelas halaman.

Mengonfigurasi beberapa titik akhir atau halaman

Buat kebijakan saat memanggil AddOutputCache untuk menentukan konfigurasi penembolokan yang berlaku untuk beberapa titik akhir. Kebijakan dapat dipilih untuk titik akhir tertentu, sementara kebijakan dasar menyediakan konfigurasi penembolokan default untuk kumpulan titik akhir.

Kode yang disorot berikut mengonfigurasi penembolokan untuk semua titik akhir aplikasi, dengan waktu kedaluwarsa 10 detik. Jika waktu kedaluwarsa tidak ditentukan, waktu defaultnya adalah satu menit.

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)));
});

Kode yang disorot berikut membuat dua kebijakan, masing-masing menentukan waktu kedaluwarsa yang berbeda. Titik akhir yang dipilih dapat menggunakan kedaluwarsa 20 detik, dan yang lain dapat menggunakan kedaluwarsa 30 detik.

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)));
});

Anda dapat memilih kebijakan untuk titik akhir saat memanggil CacheOutput metode atau menggunakan [OutputCache] atribut :

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

Untuk aplikasi dengan pengontrol, terapkan [OutputCache] atribut ke metode tindakan. Untuk Razor aplikasi Pages, terapkan atribut ke Razor kelas halaman.

Kebijakan penembolokan output default

Secara default, penembolokan output mengikuti aturan berikut:

  • Hanya respons HTTP 200 yang di-cache.
  • Hanya permintaan HTTP GET atau HEAD yang di-cache.
  • Respons yang ditetapkan cookietidak di-cache.
  • Respons terhadap permintaan terautentikasi tidak di-cache.

Kode berikut menerapkan semua aturan penembolokan default ke semua titik akhir aplikasi:

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => builder.Cache());
});

Mengesampingkan kebijakan default

Kode berikut menunjukkan cara mengambil alih aturan default. Baris yang disorot dalam kode kebijakan kustom berikut memungkinkan penembolokan untuk metode HTTP POST dan respons HTTP 301:

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;
    }
}

Untuk menggunakan kebijakan kustom ini, buat kebijakan bernama:

builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("CachePost", MyCustomPolicy.Instance);
});

Dan pilih kebijakan bernama untuk titik akhir:

app.MapPost("/cachedpost", Gravatar.WriteGravatar)
    .CacheOutput("CachePost");

Penggantian kebijakan default alternatif

Atau, gunakan Dependency Injection (DI) untuk menginisialisasi instans, dengan perubahan berikut pada kelas kebijakan kustom:

  • Konstruktor publik alih-alih konstruktor privat.
  • Hilangkan Instance properti di kelas kebijakan kustom.

Contohnya:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Sisa kelas sama seperti yang ditunjukkan sebelumnya. Tambahkan kebijakan kustom seperti yang ditunjukkan dalam contoh berikut:

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => 
        builder.AddPolicy<MyCustomPolicy2>(), true);
});

Kode sebelumnya menggunakan DI untuk membuat instans kelas kebijakan kustom. Setiap argumen publik dalam konstruktor diselesaikan.

Saat menggunakan kebijakan kustom sebagai kebijakan dasar, jangan panggil OutputCache() (tanpa argumen) pada titik akhir apa pun yang harus diterapkan oleh kebijakan dasar. OutputCache() Panggilan menambahkan kebijakan default ke titik akhir.

Tentukan kunci cache

Secara default, setiap bagian URL disertakan sebagai kunci ke entri cache, yaitu, skema, host, port, jalur, dan string kueri. Namun, Anda mungkin ingin mengontrol kunci cache secara eksplisit. Misalnya, Anda memiliki titik akhir yang mengembalikan respons unik hanya untuk setiap nilai culture unik string kueri. Variasi di bagian lain URL, seperti string kueri lainnya, tidak boleh menghasilkan entri cache yang berbeda. Anda dapat menentukan aturan tersebut dalam kebijakan, seperti yang ditunjukkan dalam kode yang disorot berikut:

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));
});

Anda kemudian dapat memilih VaryByQuery kebijakan untuk titik akhir:

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

Berikut adalah beberapa opsi untuk mengontrol kunci cache:

  • SetVaryByQuery - Tentukan satu atau beberapa nama string kueri untuk ditambahkan ke kunci cache.

  • SetVaryByHeader - Tentukan satu atau beberapa header HTTP untuk ditambahkan ke kunci cache.

  • VaryByValue- Tentukan nilai untuk ditambahkan ke kunci cache. Contoh berikut menggunakan nilai yang menunjukkan apakah waktu server saat ini dalam detik ganjil atau genap. Respons baru dihasilkan hanya ketika jumlah detik berubah dari ganjil ke genap atau bahkan ke ganjil.

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

Gunakan OutputCacheOptions.UseCaseSensitivePaths untuk menentukan bahwa bagian jalur kunci peka huruf besar/kecil. Defaultnya adalah tidak peka huruf besar/kecil.

Untuk opsi lainnya, lihat OutputCachePolicyBuilder kelas .

Validasi ulang cache

Validasi ulang cache berarti server dapat mengembalikan 304 Not Modified kode status HTTP alih-alih isi respons penuh. Kode status ini memberi tahu klien bahwa respons terhadap permintaan tidak berubah dari apa yang diterima klien sebelumnya.

Kode berikut mengilustrasikan penggunaan Etag header untuk mengaktifkan validasi ulang cache. Jika klien mengirim If-None-Match header dengan nilai etag dari respons sebelumnya, dan entri cache segar, server mengembalikan 304 Tidak Dimodifikasi alih-alih respons penuh:

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

}).CacheOutput();

Cara lain untuk melakukan validasi ulang cache adalah dengan memeriksa tanggal pembuatan entri cache dibandingkan dengan tanggal yang diminta oleh klien. Saat header If-Modified-Since permintaan disediakan, penembolokan output mengembalikan 304 jika entri yang di-cache lebih lama dan tidak kedaluwarsa.

Validasi ulang cache otomatis sebagai respons terhadap header ini yang dikirim dari klien. Tidak ada konfigurasi khusus yang diperlukan pada server untuk mengaktifkan perilaku ini, selain mengaktifkan penembolokan output.

Menggunakan tag untuk mengeluarkan entri cache

Anda dapat menggunakan tag untuk mengidentifikasi sekelompok titik akhir dan mengeluarkan semua entri cache untuk grup. Misalnya, kode berikut membuat sepasang titik akhir yang URL-nya dimulai dengan "blog", dan menandainya "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")); ;

Cara alternatif untuk menetapkan tag untuk pasangan titik akhir yang sama adalah dengan menentukan kebijakan dasar yang berlaku untuk titik akhir yang dimulai dengan blog:

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));
});

Alternatif lain adalah memanggil MapGroup:

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

Dalam contoh penetapan tag sebelumnya, kedua titik akhir diidentifikasi oleh tag-blog tag. Anda kemudian dapat mengeluarkan entri cache untuk titik akhir tersebut dengan satu pernyataan yang mereferensikan tag tersebut:

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

Dengan kode ini, permintaan HTTP POST yang dikirim untuk https://localhost:<port>/purge/tag-blog mengeluarkan entri cache untuk titik akhir ini.

Anda mungkin ingin cara untuk mengeluarkan semua entri cache untuk semua titik akhir. Untuk melakukannya, buat kebijakan dasar untuk semua titik akhir seperti yang dilakukan kode berikut:

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));
});

Kebijakan dasar ini memungkinkan Anda menggunakan tag "tag-all" untuk mengeluarkan semuanya dalam cache.

Menonaktifkan penguncian sumber daya

Secara default, penguncian sumber daya diaktifkan untuk mengurangi risiko cache stampede dan kawanan guntur. Untuk informasi selengkapnya, lihat Penembolokan Output.

Untuk menonaktifkan penguncian sumber daya, panggil SetLocking(false) saat membuat kebijakan, seperti yang ditunjukkan dalam contoh berikut:

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));
});

Contoh berikut memilih kebijakan tanpa penguncian untuk titik akhir:

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

Batas

Properti OutputCacheOptions berikut memungkinkan Anda mengonfigurasi batas yang berlaku untuk semua titik akhir:

  • SizeLimit - Ukuran maksimum penyimpanan cache. Ketika batas ini tercapai, tidak ada respons baru yang di-cache hingga entri yang lebih lama dikeluarkan. Nilai defaultnya adalah 100 MB.
  • MaximumBodySize - Jika isi respons melebihi batas ini, isi tidak di-cache. Nilai defaultnya adalah 64 MB.
  • DefaultExpirationTimeSpan - Durasi waktu kedaluwarsa yang berlaku ketika tidak ditentukan oleh kebijakan. Nilai defaultnya adalah 60 detik.

Penyimpanan cache

IOutputCacheStore digunakan untuk penyimpanan. Secara default digunakan dengan MemoryCache. Respons cache disimpan dalam proses, sehingga setiap server memiliki cache terpisah yang hilang setiap kali proses server dimulai ulang.

Cache Redis

Alternatifnya adalah menggunakan cache Redis . Cache Redis memberikan konsistensi antara simpul server melalui cache bersama yang keluar dari proses server individual. Untuk menggunakan Redis untuk penembolokan output:

  • Instal paket NuGet Microsoft.AspNetCore.OutputCaching.StackExchangeRedis.

  • Panggil builder.Services.AddStackExchangeRedisOutputCache (bukan AddStackExchangeRedisCache), dan berikan string koneksi yang menunjuk ke server Redis.

    Contohnya:

    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- String koneksi ke server Redis lokal atau ke penawaran yang dihosting seperti Azure Cache for Redis. Misalnya, <instance_name>.redis.cache.windows.net:6380,password=<password>,ssl=True,abortConnect=False untuk Azure cache for Redis.
    • options.InstanceName - Opsional, menentukan partisi logis untuk cache.

    Opsi konfigurasi identik dengan opsi penembolokan terdistribusi berbasis Redis.

Kami tidak merekomendasikan IDistributedCache untuk digunakan dengan penembolokan output. IDistributedCache tidak memiliki fitur atomik, yang diperlukan untuk pemberian tag. Kami menyarankan agar Anda menggunakan dukungan bawaan untuk Redis atau membuat implementasi kustom IOutputCacheStore dengan menggunakan dependensi langsung pada mekanisme penyimpanan yang mendasar.

Lihat juga

Artikel ini menjelaskan cara mengonfigurasi middleware penembolokan output di aplikasi ASP.NET Core. Untuk pengenalan penembolokan output, lihat Penembolokan output.

Middleware penembolokan output dapat digunakan di semua jenis aplikasi ASP.NET Core: API Minimal, API Web dengan pengontrol, MVC, dan Razor Pages. Aplikasi sampel adalah API Minimal, tetapi setiap fitur penembolokan yang diilustrasikannya juga didukung di jenis aplikasi lain.

Menambahkan middleware ke aplikasi

Tambahkan middleware penembolokan output ke kumpulan layanan dengan memanggil AddOutputCache.

Tambahkan middleware ke alur pemrosesan permintaan dengan memanggil UseOutputCache.

Catatan

  • Dalam aplikasi yang menggunakan middleware CORS, UseOutputCache harus dipanggil setelah UseCors.
  • Di Razor aplikasi dan aplikasi Pages dengan pengontrol, UseOutputCache harus dipanggil setelah UseRouting.
  • Memanggil AddOutputCachedan UseOutputCache tidak memulai perilaku penembolokan, membuat penembolokan tersedia. Data respons penembolokan harus dikonfigurasi seperti yang ditunjukkan di bagian berikut.

Mengonfigurasi satu titik akhir atau halaman

Untuk aplikasi API minimal, konfigurasikan titik akhir untuk melakukan penembolokan dengan memanggil CacheOutput, atau dengan menerapkan [OutputCache] atribut , seperti yang ditunjukkan dalam contoh berikut:

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

Untuk aplikasi dengan pengontrol, terapkan [OutputCache] atribut ke metode tindakan. Untuk Razor aplikasi Pages, terapkan atribut ke Razor kelas halaman.

Mengonfigurasi beberapa titik akhir atau halaman

Buat kebijakan saat memanggil AddOutputCache untuk menentukan konfigurasi penembolokan yang berlaku untuk beberapa titik akhir. Kebijakan dapat dipilih untuk titik akhir tertentu, sementara kebijakan dasar menyediakan konfigurasi penembolokan default untuk kumpulan titik akhir.

Kode yang disorot berikut mengonfigurasi penembolokan untuk semua titik akhir aplikasi, dengan waktu kedaluwarsa 10 detik. Jika waktu kedaluwarsa tidak ditentukan, waktu defaultnya adalah satu menit.

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)));
});

Kode yang disorot berikut membuat dua kebijakan, masing-masing menentukan waktu kedaluwarsa yang berbeda. Titik akhir yang dipilih dapat menggunakan kedaluwarsa 20 detik, dan yang lain dapat menggunakan kedaluwarsa 30 detik.

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)));
});

Anda dapat memilih kebijakan untuk titik akhir saat memanggil CacheOutput metode atau menggunakan [OutputCache] atribut :

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

Untuk aplikasi dengan pengontrol, terapkan [OutputCache] atribut ke metode tindakan. Untuk Razor aplikasi Pages, terapkan atribut ke Razor kelas halaman.

Kebijakan penembolokan output default

Secara default, penembolokan output mengikuti aturan berikut:

  • Hanya respons HTTP 200 yang di-cache.
  • Hanya permintaan HTTP GET atau HEAD yang di-cache.
  • Respons yang ditetapkan cookietidak di-cache.
  • Respons terhadap permintaan terautentikasi tidak di-cache.

Kode berikut menerapkan semua aturan penembolokan default ke semua titik akhir aplikasi:

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => builder.Cache());
});

Mengesampingkan kebijakan default

Kode berikut menunjukkan cara mengambil alih aturan default. Baris yang disorot dalam kode kebijakan kustom berikut memungkinkan penembolokan untuk metode HTTP POST dan respons HTTP 301:

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;
    }
}

Untuk menggunakan kebijakan kustom ini, buat kebijakan bernama:

builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("CachePost", MyCustomPolicy.Instance);
});

Dan pilih kebijakan bernama untuk titik akhir:

app.MapPost("/cachedpost", Gravatar.WriteGravatar)
    .CacheOutput("CachePost");

Penggantian kebijakan default alternatif

Atau, gunakan Dependency Injection (DI) untuk menginisialisasi instans, dengan perubahan berikut pada kelas kebijakan kustom:

  • Konstruktor publik alih-alih konstruktor privat.
  • Hilangkan Instance properti di kelas kebijakan kustom.

Contohnya:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Sisa kelas sama seperti yang ditunjukkan sebelumnya. Tambahkan kebijakan kustom seperti yang ditunjukkan dalam contoh berikut:

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => 
        builder.AddPolicy<MyCustomPolicy2>(), true);
});

Kode sebelumnya menggunakan DI untuk membuat instans kelas kebijakan kustom. Setiap argumen publik dalam konstruktor diselesaikan.

Saat menggunakan kebijakan kustom sebagai kebijakan dasar, jangan panggil OutputCache() (tanpa argumen) pada titik akhir apa pun yang harus diterapkan oleh kebijakan dasar. OutputCache() Panggilan menambahkan kebijakan default ke titik akhir.

Tentukan kunci cache

Secara default, setiap bagian URL disertakan sebagai kunci ke entri cache, yaitu, skema, host, port, jalur, dan string kueri. Namun, Anda mungkin ingin mengontrol kunci cache secara eksplisit. Misalnya, Anda memiliki titik akhir yang mengembalikan respons unik hanya untuk setiap nilai culture unik string kueri. Variasi di bagian lain URL, seperti string kueri lainnya, tidak boleh menghasilkan entri cache yang berbeda. Anda dapat menentukan aturan tersebut dalam kebijakan, seperti yang ditunjukkan dalam kode yang disorot berikut:

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));
});

Anda kemudian dapat memilih VaryByQuery kebijakan untuk titik akhir:

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

Berikut adalah beberapa opsi untuk mengontrol kunci cache:

  • SetVaryByQuery - Tentukan satu atau beberapa nama string kueri untuk ditambahkan ke kunci cache.

  • SetVaryByHeader - Tentukan satu atau beberapa header HTTP untuk ditambahkan ke kunci cache.

  • VaryByValue- Tentukan nilai untuk ditambahkan ke kunci cache. Contoh berikut menggunakan nilai yang menunjukkan apakah waktu server saat ini dalam detik ganjil atau genap. Respons baru dihasilkan hanya ketika jumlah detik berubah dari ganjil ke genap atau bahkan ke ganjil.

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

Gunakan OutputCacheOptions.UseCaseSensitivePaths untuk menentukan bahwa bagian jalur kunci peka huruf besar/kecil. Defaultnya adalah tidak peka huruf besar/kecil.

Untuk opsi lainnya, lihat OutputCachePolicyBuilder kelas .

Validasi ulang cache

Validasi ulang cache berarti server dapat mengembalikan 304 Not Modified kode status HTTP alih-alih isi respons penuh. Kode status ini memberi tahu klien bahwa respons terhadap permintaan tidak berubah dari apa yang diterima klien sebelumnya.

Kode berikut mengilustrasikan penggunaan Etag header untuk mengaktifkan validasi ulang cache. Jika klien mengirim If-None-Match header dengan nilai etag dari respons sebelumnya, dan entri cache segar, server mengembalikan 304 Tidak Dimodifikasi alih-alih respons penuh:

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

}).CacheOutput();

Cara lain untuk melakukan validasi ulang cache adalah dengan memeriksa tanggal pembuatan entri cache dibandingkan dengan tanggal yang diminta oleh klien. Saat header If-Modified-Since permintaan disediakan, penembolokan output mengembalikan 304 jika entri yang di-cache lebih lama dan tidak kedaluwarsa.

Validasi ulang cache otomatis sebagai respons terhadap header ini yang dikirim dari klien. Tidak ada konfigurasi khusus yang diperlukan pada server untuk mengaktifkan perilaku ini, selain mengaktifkan penembolokan output.

Menggunakan tag untuk mengeluarkan entri cache

Anda dapat menggunakan tag untuk mengidentifikasi sekelompok titik akhir dan mengeluarkan semua entri cache untuk grup. Misalnya, kode berikut membuat sepasang titik akhir yang URL-nya dimulai dengan "blog", dan menandainya "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")); ;

Cara alternatif untuk menetapkan tag untuk pasangan titik akhir yang sama adalah dengan menentukan kebijakan dasar yang berlaku untuk titik akhir yang dimulai dengan blog:

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));
});

Alternatif lain adalah memanggil MapGroup:

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

Dalam contoh penetapan tag sebelumnya, kedua titik akhir diidentifikasi oleh tag-blog tag. Anda kemudian dapat mengeluarkan entri cache untuk titik akhir tersebut dengan satu pernyataan yang mereferensikan tag tersebut:

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

Dengan kode ini, permintaan HTTP POST yang dikirim akan https://localhost:<port>/purge/tag-blog mengeluarkan entri cache untuk titik akhir ini.

Anda mungkin ingin cara untuk mengeluarkan semua entri cache untuk semua titik akhir. Untuk melakukannya, buat kebijakan dasar untuk semua titik akhir seperti yang dilakukan kode berikut:

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));
});

Kebijakan dasar ini memungkinkan Anda menggunakan tag "tag-all" untuk mengeluarkan semuanya dalam cache.

Menonaktifkan penguncian sumber daya

Secara default, penguncian sumber daya diaktifkan untuk mengurangi risiko cache stampede dan kawanan guntur. Untuk informasi selengkapnya, lihat Penembolokan Output.

Untuk menonaktifkan penguncian sumber daya, panggil SetLocking(false) saat membuat kebijakan, seperti yang ditunjukkan dalam contoh berikut:

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));
});

Contoh berikut memilih kebijakan tanpa penguncian untuk titik akhir:

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

Batas

Properti OutputCacheOptions berikut memungkinkan Anda mengonfigurasi batas yang berlaku untuk semua titik akhir:

  • SizeLimit - Ukuran maksimum penyimpanan cache. Ketika batas ini tercapai, tidak ada respons baru yang akan di-cache hingga entri yang lebih lama dikeluarkan. Nilai defaultnya adalah 100 MB.
  • MaximumBodySize - Jika isi respons melebihi batas ini, itu tidak akan di-cache. Nilai defaultnya adalah 64 MB.
  • DefaultExpirationTimeSpan - Durasi waktu kedaluwarsa yang berlaku ketika tidak ditentukan oleh kebijakan. Nilai defaultnya adalah 60 detik.

Penyimpanan cache

IOutputCacheStore digunakan untuk penyimpanan. Secara default digunakan dengan MemoryCache. Kami tidak merekomendasikan IDistributedCache untuk digunakan dengan penembolokan output. IDistributedCache tidak memiliki fitur atomik, yang diperlukan untuk pemberian tag. Kami menyarankan agar Anda membuat implementasi kustom IOutputCacheStore dengan menggunakan dependensi langsung pada mekanisme penyimpanan yang mendasar, seperti Redis. Atau gunakan dukungan bawaan untuk cache Redis di .NET 8..

Lihat juga