出力キャッシュでパフォーマンスを改善する (C#)

提供元: Microsoft

このチュートリアルでは、出力キャッシュを利用して ASP.NET MVC Web アプリケーションのパフォーマンスを大幅に改善する方法について説明します。 コントローラー アクションから返された結果をキャッシュして、新しいユーザーがそのアクションを呼び出すたびに同じコンテンツを作成しなくて済むようにする方法について説明します。

このチュートリアルの目的は、出力キャッシュを利用して ASP.NET MVC アプリケーションのパフォーマンスを大幅に改善する方法を説明することです。 出力キャッシュを使うと、コントローラー アクションから返されたコンテンツをキャッシュできます。 これにより、同じコントローラー アクションが呼び出されるたびに同じコンテンツを生成する必要がなくなります。

たとえば、ASP.NET MVC アプリケーションの Index というビューで、データベース レコードの一覧を表示するとします。 通常は、ユーザーが Index ビューを返すコントローラー アクションを呼び出すたびに、データベース クエリを実行して一連のデータベース レコードをデータベースから取得する必要があります。

一方、出力キャッシュを利用する場合は、ユーザーが同じコントローラー アクションを呼び出すたびにデータベース クエリを実行することを回避できます。 そのビューは、コントローラー アクションで再生成するのではなく、キャッシュから取得できます。 キャッシュを使うと、サーバー上での冗長な処理の実行を回避できます。

出力キャッシュの有効化

出力キャッシュを有効にするには、[OutputCache] 属性を個々のコントローラー アクションまたはコントローラー クラス全体に追加します。 たとえば、リスト 1 のコントローラーは Index() という名前のアクションを公開します。 この Index() アクションの出力は 10 秒間キャッシュされます。

リスト 1 – Controllers\HomeController.cs

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        [OutputCache(Duration=10, VaryByParam="none")]
        public ActionResult Index()
        {
            return View();
        }
    }
}

ASP.NET MVC のベータ版では、出力キャッシュは http://www.MySite.com/ のような URL では機能しません。 代わりに、http://www.MySite.com/Home/Index のような URL を入力する必要があります。

リスト 1 の Index() アクションの出力は 10 秒間キャッシュされます。 必要に応じて、より長いキャッシュ期間を指定できます。 たとえば、コントローラー アクションの出力を 1 日間キャッシュしたい場合は、86400 秒 (60 秒 * 60 分 * 24 時間) のキャッシュ期間を指定できます。

指定した期間の間コンテンツがキャッシュされる保証はありません。 メモリ リソースが不足すると、キャッシュは自動的にコンテンツの削除を開始します。

リスト 1 の Home コントローラーは、リスト 2 の Index ビューを返します。 このビューに関して特別な点はありません。 この Index ビューは、単に現在の時刻を表示します (図 1 を参照)。

リスト 2 - \Views\Home\Index.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
</head>
<body>
    <div>
    
    The current time is: <%= DateTime.Now.ToString("T") %>
    
    
    </div>
</body>
</html>

図 1 – キャッシュされた Index ビュー

clip_image002

ブラウザーのアドレス バーに /Home/Index という URL を入力し、ブラウザーの更新/再読み込みボタンを繰り返し押して Index() アクションを複数回呼び出した場合、Index ビューに表示される時刻は 10 秒間変化しません。 ビューがキャッシュされるため、同じ時刻が表示されます。

アプリケーションにアクセスするすべてのユーザーに対して同じビューがキャッシュされることを理解することが重要です。 Index() アクションを呼び出したユーザーの全員が、同じキャッシュされたバージョンの Index ビューを取得します。 これは、Index ビューを提供するために Web サーバーが実行する必要がある処理量が大幅に減ることを意味します。

リスト 2 のビューは、たまたま非常に単純な処理を行っています。 このビューは現在の時刻を表示するだけです。 しかし、一連のデータベース レコードを表示するビューをキャッシュすることも同じように簡単にできます。 その場合、ビューを返すコントローラー アクションが呼び出されるたびに、データベースから一連のデータベース レコードを取得する必要はなくなります。 キャッシュを使うと、Web サーバーとデータベース サーバー両方の処理量を減らすことができます。

MVC ビューでは page <%@ OutputCache %> ディレクティブを使用しないでください。 このディレクティブは Web Forms の分野から派生したものであり、ASP.NET MVC アプリケーションで使用すべきではありません。

コンテンツがキャッシュされる場所

既定では、[OutputCache] 属性を使う場合、コンテンツは Web サーバー、任意のプロキシ サーバー、Web ブラウザーという 3 つの場所にキャッシュされます。 [OutputCache] 属性の Location プロパティを変更することで、コンテンツがキャッシュされる場所を厳密に制御できます。

Location プロパティは、次のいずれかの値に設定できます。

· Any

· Client

· Downstream

· Server

· None

· ServerAndClient

Location プロパティの既定値は Any です。 しかし、ブラウザーまたはサーバーにのみキャッシュしたい場合があります。 たとえば、ユーザーごとにパーソナライズされた情報をキャッシュする場合は、その情報をサーバーにキャッシュすべきではありません。 異なるユーザーに異なる情報を表示する場合は、クライアントにのみ情報をキャッシュする必要があります。

たとえば、リスト 3 のコントローラーは、現在のユーザー名を返す GetName() という名前のアクションを公開します。 Jack がこの Web サイトにログインして GetName() アクションを呼び出すと、"Hi Jack" という文字列がアクションから返されます。 その後、Jill がこの Web サイトにログインして GetName() アクションを呼び出した場合も、"Hi Jack" という文字列が返されます。 この文字列は、Jack が最初にコントローラー アクションを呼び出した後、すべてのユーザーに対して Web サーバーにキャッシュされます。

リスト 3 – Controllers\BadUserController.cs

using System.Web.Mvc;
using System.Web.UI;

namespace MvcApplication1.Controllers
{
    public class BadUserController : Controller
    {
        [OutputCache(Duration = 3600, VaryByParam = "none")]
        public string GetName()
        {
            return "Hi " + User.Identity.Name;
        }
    }
}

ほとんどの場合、このリスト 3 のコントローラーは望ましい動作とはいえません。 Jill に "Hi Jack" というメッセージを表示すべきではありません。

パーソナライズされたコンテンツはサーバー キャッシュにキャッシュすべきではありません。 ただし、パフォーマンスを改善するために、パーソナライズされたコンテンツをブラウザー キャッシュにキャッシュすることが必要な場合があります。 ブラウザーにコンテンツをキャッシュし、ユーザーが同じコントローラー アクションを複数回呼び出す場合は、サーバーではなくブラウザー キャッシュからコンテンツを取得できます。

リスト 4 の修正されたコントローラーは、GetName() アクションの出力をキャッシュします。 ただし、そのコンテンツはブラウザーにのみキャッシュされ、サーバーにはキャッシュされません。 こうすることで、複数のユーザーが GetName() メソッドを呼び出しても、各ユーザーが別のユーザー名ではなく自分のユーザー名を取得できます。

リスト 4 – Controllers\UserController.cs

using System.Web.Mvc;
using System.Web.UI;

namespace MvcApplication1.Controllers
{
    public class UserController : Controller
    {
        [OutputCache(Duration=3600, VaryByParam="none", Location=OutputCacheLocation.Client, NoStore=true)]
        public string GetName()
        {
            return "Hi " + User.Identity.Name;
        }
    }
}

リスト 4 の [OutputCache] 属性には、OutputCacheLocation.Client という値に設定された Location プロパティが含まれていることに注意してください。 [OutputCache] 属性には、NoStore プロパティも含まれています。 NoStore プロパティを使うと、プロキシ サーバーとブラウザーに対して、キャッシュされたコンテンツの永続的なコピーを保存しないように伝えることができます。

出力キャッシュを変化させる

状況によっては、まったく同じコンテンツの異なるキャッシュ バージョンが必要な場合があります。 たとえば、マスター/詳細ページを作成しているとします。 マスター ページには、映画タイトルの一覧が表示されます。 あるタイトルをクリックすると、選択した映画の詳細が表示されます。

この詳細ページをキャッシュすると、クリックした映画に関係なく、同じ映画の詳細が表示されます。 最初のユーザーが選択した最初の映画が、以降のすべてのユーザーに表示されます。

この問題を解決するには、[OutputCache] 属性の VaryByParam プロパティを利用します。 このプロパティを使うと、フォーム パラメーターまたはクエリ文字列パラメーターが変化する場合に、まったく同じコンテンツの異なるキャッシュ バージョンを作成できます。

たとえば、リスト 5 のコントローラーは、Master() と Details() という 2 つのアクションを公開します。 Master() アクションは映画タイトルの一覧を返し、Details() アクションは選択した映画の詳細を返します。

リスト 5 – Controllers\MoviesController.cs

using System.Linq;
using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class MoviesController : Controller
    {
        private MovieDataContext _dataContext;

        public MoviesController()
        {
            _dataContext = new MovieDataContext();
        }

        [OutputCache(Duration=int.MaxValue, VaryByParam="none")]
        public ActionResult Master()
        {
            ViewData.Model = (from m in _dataContext.Movies 
                              select m).ToList();
            return View();
        }

        [OutputCache(Duration = int.MaxValue, VaryByParam = "id")]
        public ActionResult Details(int id)
        {
            ViewData.Model = _dataContext.Movies.SingleOrDefault(m => m.Id == id);
            return View();
        }


    }
}

Master() アクションには、値 "none" の VaryByParam プロパティが含まれています。 この Master() アクションが呼び出されると、Master ビューの同じキャッシュ バージョンが返されます。 フォーム パラメーターやクエリ文字列パラメーターはすべて無視されます (図 2 を参照)。

図 2 – /Movies/Master ビュー

clip_image004

図 3 – /Movies/Details ビュー

clip_image006

Details() アクションには、値 "Id" の VaryByParam プロパティが含まれています。 異なる値の Id パラメーターがこのコントローラー アクションに渡されると、Details ビューの異なるキャッシュ バージョンが生成されます。

VaryByParam プロパティを使うと、キャッシュが減るのではなく増えることを理解することが重要です。 Id パラメーターの異なるバージョンごとに、Details ビューの異なるキャッシュ バージョンが作成されます。

VaryByParam プロパティは、次の値に設定できます。

* = フォームまたはクエリ文字列パラメーターが変化するたびに、異なるキャッシュ バージョンを作成します。

none = 異なるキャッシュ バージョンを作成しません

セミコロンで区切ったパラメーターのリスト = リスト内のいずれかのフォームまたはクエリ文字列パラメーターが変化するたびに、異なるキャッシュ バージョンを作成します

キャッシュ プロファイルの作成

[OutputCache] 属性のプロパティを変更して出力キャッシュ プロパティを構成する代替手段として、Web 構成 (web.config) ファイルにキャッシュ プロファイルを作成できます。 Web 構成ファイルにキャッシュ プロファイルを作成する場合、いくつかの重要な利点があります。

まず、Web 構成ファイルで出力キャッシュを構成することで、各コントローラー アクションがコンテンツをキャッシュする方法を 1 か所で集中制御できます。 1 つのキャッシュ プロファイルを作成し、そのプロファイルを複数のコントローラーまたはコントローラー アクションに適用できます。

次に、アプリケーションを再コンパイルせずに Web 構成ファイルを変更できます。 既に運用環境にデプロイしているアプリケーションのキャッシュを無効にする必要がある場合は、Web 構成ファイルで定義されているキャッシュ プロファイルを変更するだけで済みます。 Web 構成ファイルへの変更はすべて自動的に検出され、適用されます。

たとえば、リスト 6 の <caching> Web 構成セクションでは、Cache1Hour という名前のキャッシュ プロファイルが定義されています。 <caching> セクションは、Web 構成ファイルの <system.web> セクション内に含まれている必要があります。

リスト 6 – web.config の caching セクション

<caching>
<outputCacheSettings>
    <outputCacheProfiles>
        <add name="Cache1Hour" duration="3600" varyByParam="none"/>
    </outputCacheProfiles>
</outputCacheSettings>
</caching>

リスト 7 のコントローラーは、[OutputCache] 属性を持つコントローラー アクションに Cache1Hour プロファイルを適用する方法を示しています。

リスト 7 – Controllers\ProfileController.cs

using System;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class ProfileController : Controller
    {
        [OutputCache(CacheProfile="Cache1Hour")]
        public string Index()
        {
            return DateTime.Now.ToString("T");
        }
    }
}

リスト 7 のコントローラーが公開する Index() アクションを呼び出すと、1 時間にわたって同じ時刻が返されます。

まとめ

出力キャッシュを使うと、非常に簡単な方法で ASP.NET MVC アプリケーションのパフォーマンスを大幅に改善することができます。 このチュートリアルでは、[OutputCache] 属性を使ってコントローラー アクションの出力をキャッシュする方法について説明しました。 また、[OutputCache] 属性のプロパティ (Duration プロパティや VaryByParam プロパティなど) を変更して、コンテンツのキャッシュ方法を変更する方法についても説明しました。 最後に、Web 構成ファイルでキャッシュ プロファイルを定義する方法について説明しました。