しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core MVC 1.0 でキャッシュを行う 3 つの方法

ASP.NET MVC 5 でもキャッシュのための手段はいくつか用意されていましたが、ASP.NET Core MVC 1.0 では少し方向性が変わっています。大きく変わっているのが OutputCache 属性が無くなっていることです。

現実的にキャッシュを行う手段は ASP.NET Core MVC 1.0 では以下になります。

それぞれ特徴があるのと、MVC 5 には無かった機能なので一つずつ確認してみました。

ResponseCache

OutputCache みたいな名前の ResponseCache ですが、中身は全く異なっています。OutputCache は出力をサーバー側でキャッシュしていましたが、ResponseCache は Cache-Control ヘッダーを出力するだけです。

[ResponseCache(Duration = 10)]
public IActionResult Index()
{
    return View();
}

10 秒だけキャッシュするように設定すると、Cache-Control の max-age が設定されています。

あくまでもブラウザ側にキャッシュさせるだけなので、Ctrl+F5 などで更新するとキャッシュが無視されて、サーバーにリクエストが投げられます。

ASP.NET は Cache-Control 周りが上手くコントロールできないことが多かったですが、ASP.NET Core は基本的に開発者がコントロールできるようになっているので、挙動が分かりやすいです。

Cache Tag Helper

コンテンツ全体をキャッシュする OutputCache 的なものは RC 2 でも用意されてないですが、コンテンツ内の一部分だけをキャッシュするための Tag Helper は用意されています。

Razor では cache タグを使うだけで、その中身をキャッシュ可能です。

<p>Actual Time: @DateTime.Now.ToString()</p>

<cache expires-after="TimeSpan.FromSeconds(10)">
    <p>Cached Time: @DateTime.Now.ToString()</p>
</cache>

日付を扱う単純なサンプルですが、これを実行するとキャッシュされていることが確認できます。

実際にはページコンテンツ全体をキャッシュするよりも、一部分だけをキャッシュしたいことのが多いと思うので、Cache Tag Helper は便利に扱えそうです。

中に View Components の呼び出しを入れると、MVC 5 などで可能だった Action/RenderAction を使った部分的なキャッシュを行えるようになってます。

Memory Cache

.NET Framework 4 からは System.Runtime.Caching が追加されて、MemoryCache を使えるようになりましたが、ObjectCache のオーバーライドめんどくさくて大変でした。

ASP.NET Core 1.0 では Microsoft.Extension.Caching が追加されて、シンプルですが拡張性の高いインターフェースを備えるようになりました。

Cache in-memory in ASP.NET Core | Microsoft Learn

公式で In Memory 以外にも Redis と SQL Server を使えるようになっているのもポイントが高いです。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMemoryCache();
}

使う前にはちゃんと ConfigureServices で AddMemoryCache メソッドを呼び出しておきます。

インスタンスを取得するには、コンストラクタで IMemoryCache を受け取るようにするだけです。

public class HomeController : Controller
{
    public HomeController(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    private IMemoryCache _memoryCache;

    public async Task<IActionResult> Index()
    {
        var data = await _memoryCache.GetOrCreateAsync("KEY", async entry =>
        {
            await Task.Delay(1000);

            // 5 秒だけキャッシュする
            entry.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(5);

            return DateTime.Now.ToString();
        });

        ViewData["Message"] = data;

        return View();
    }
}

非同期対応のキャッシュインターフェースはこれまで公式で提供されていなかったので、自作した人が多いと思います。Task と async が使える拡張メソッドが用意されているので、かなり便利です。

Memory Cache はデータベースや、ネットワーク経由で取得したデータのキャッシュ向けでしょうね。