Повышение производительности посредством кэширования вывода

Это руководство на языке C# (перейти к руководству на Visual Basic)

В этом руководстве описывается использование кэширования вывода, которое значительно повышает производительность приложений ASP.NET MVC. Из этого руководства вы узнаете, как кэшировать результаты, возвращаемые действием контроллера, чтобы при повторном вызове действия пользователем не требовалось создавать тот же самый контент.

Цель этого руководства — показать, как использовать преимущества кэширования вывода для значительного повышения производительности приложения 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 кэширование вывода не поддерживается для URL-адресов вида http://www.MySite.com/. Вместо URL-адресов этого вида необходимо использовать URL-адреса вида http://www.MySite.com/Home/Index.

В примере кода 1 выходные данные действия Index() кэшируются в течение 10 секунд. При необходимости можно указать более продолжительный период кэширования. Например, если требуется кэшировать выходные данные действия контроллера в течение суток, необходимо указать период кэширования равный 86 400 секундам (60 секунд * 60 минут * 24 часа).

Однако это не гарантирует, что контент будет кэшироваться в течение указанного периода времени. Если ресурсов памяти недостаточно, кэш автоматически удаляет контент.

Контроллер Home в примере кода 1 возвращает представление Index в примере кода 2. Это представление реализовано достаточно стандартно. Представление 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>
   
    Текущее время: <%= DateTime.Now.ToString("T") %>
    </div>
</body>
</html>

Рис. 1. Кэшированное представление Index

Если действие Index() вызывается несколько раз путем ввода URL-адреса /Home/Index в адресной строке браузера и путем многократного нажатия кнопки «Обновить» или «Перезагрузить» в браузере, время, отображаемое представлением Index, не будет изменяться в течение 10 секунд. Поскольку представление кэшировано, будет отображаться одно и то же время.

Следует отметить, что для всех посетителей веб-страницы кэшируется одно и то же представление. Любой пользователь, вызывающий действие Index(), получит такую же кэшированную версию представления Index, что и другие пользователи. Это означает, что объем работы, выполняемый веб-сервером по обслуживанию представления Index, существенно сокращается.

Представление в примере кода 2 выполняет действительно очень простое действие. Оно просто отображает текущее время. Однако этот способ можно использовать для кэширования представления, которое отображает набор записей базы данных. В этом случае не потребуется извлекать набор записей из базы данных при каждом вызове действия контроллера, которое возвращает представление. Кэширование позволяет снизить нагрузку на веб-сервер и сервер баз данных.

В представлении MVC не рекомендуется использовать директиву страницы <%@ OutputCache %> . Эта директива относится к веб-формам и не должна использоваться в приложении ASP.NET MVC.

Место кэширования контента

По умолчанию при использовании атрибута [OutputCache] контент кэшируется в трех расположениях: на веб-сервере, на любых прокси-серверах и в веб-браузере. Чтобы точно указать, где должен кэшироваться контент, необходимо изменить значение свойства Location атрибута [OutputCache].

Свойство Location может принимать одно из следующих значений:

  • Any
  • Client
  • Downstream
  • Server
  • None
  • ServerAndClient

По умолчанию свойство Location имеет значение Any. Однако в некоторых случаях может потребоваться кэшировать контент только в веб-браузере или только на сервере. Например, если кэшируются личные сведения пользователя, сервер является неподходящим расположением для кэширования. Если для разных пользователей отображаются разные данные, тогда необходимо кэшировать данные только на стороне клиента.

Например, контроллер в примере кода 3 определяет действие GetName(), которое возвращает текущее имя пользователя. Если Олег выполняет вход на веб-сайт и вызывает действие GetName(), действие возвращает строку "Здравствуйте, Олег". Если затем Анна войдет на веб-сайт и вызовет действие GetName(), то она тоже увидит строку "Здравствуйте, Олег". После того, как Олег первый раз вызовет действие контроллера, строка будет кэшироваться на веб-сервере для всех пользователей.  

Пример кода 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 "Здравствуйте, " + User.Identity.Name;
        }
    }
}

Контроллер в примере кода 3 работает не так, как хотелось бы. Крайне нежелательно, чтобы для Анны отображалось сообщение "Здравствуйте, Олег".

Личные сведения не должны кэшироваться на сервере. Однако для повышения производительности можно кэшировать личные сведения в кэше веб-браузера. Если контент кэшируется в веб-браузере и пользователь вызывает одно и то же действие контроллера несколько раз, контент может быть извлечен из кэша веб-браузера, а не из кэша сервера.

Измененный контроллер в примере кода 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 "Здравствуйте, " + User.Identity.Name;
        }
    }
}

Обратите внимание, что в примере кода 4 атрибут [OutputCache] содержит свойство Location со значением OutputCacheLocation.Client. Атрибут [OutputCache] также содержит свойство NoStore. Свойство NoStore используется для указания прокси-серверам и веб-браузеру не сохранять постоянную копию кэшированного контента.

Изменение кэширования вывода

В некоторых случаях могут понадобиться разные версии одного и того же кэшированного контента. Например, представьте, что вы создаете главную страницу и страницу сведений. Главная страница отображает список названий фильмов. При щелчке названия фильма отображаются сведения о выбранном фильме.

Если кэшировать страницу сведений, то независимо от того, какой фильм выбран, будет отображаться страница сведений для одного и того же фильма. Для всех последующих посетителей будут отображаться сведения о фильме, выбранном первым посетителем.

Чтобы устранить эту проблему, необходимо воспользоваться свойством VaryByParam атрибута [OutputCache]. Это свойство позволяет создавать различные версии одного и того же кэшированного контента, если параметр формы или параметр строки запроса отличается.

Например, в контроллере, приведенном в примере кода 5, определены два действия Master() и Details(). Действие 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() содержит свойство VaryByParam, которое имеет значение none. При вызове действия Master() возвращается одна и та же кэшированная версия представления Master. Любые параметры формы или параметры строки запроса игнорируются (см. рис. 2).

Рис. 2. Представление /Movies/Master

Рис. 3. Представление /Movies/Details

Действие Details() содержит свойство VaryByParam, которое имеет значение Id. Если действию контроллера передаются разные значения параметра Id, создаются разные кэшированные версии представления Details.

Важно понимать, чтобы при использовании свойства VaryByParam объем кэшируемого контента возрастает, а не уменьшается. Для разных версий параметра Id создаются разные кэшированные версии представления Details.

Свойство VaryByParam может принимать следующие значения:

* = создавать разные кэшированные версии, когда параметры формы или строки запроса отличаются.

none = никогда не создавать разные кэшированные версии.

Список параметров, разделенных точкой с запятой = создавать разные кэшированные версии, когда любые параметры формы или строки запроса в списке отличаются.

Создание профиля кэша

Вместо настройки свойств кэширования вывода путем изменения свойств атрибута [OutputCache] можно создать профиль кэша в файле веб-конфигурации (web.config). Создание профиля кэша в файле веб-конфигурации дает два важных преимущества.

Во-первых, при настройке кэширования вывода в файле веб-конфигурации можно управлять кэшированием для действий контроллера централизованно. Можно создать один профиль кэша и применить профиль к нескольким контроллерам или действиям контроллера.

Во-вторых, файл веб-конфигурации можно изменять без повторного компилирования приложения. Если требуется отключить кэширование в приложении, которое уже развернуто в производственной среде, можно просто изменить профили кэша, определенные в файле веб-конфигурации. Любые изменения файла веб-конфигурации обнаруживаются и применяются автоматически.

Например, раздел файла веб-конфигурации <caching>, приведенный в примере кода 6, определяет профиль кэша с именем Cache1Hour. Раздел <caching> должен находиться внутри раздела <system.web> в файле веб-конфигурации.

Пример кода 6. Раздел кэширования файла web.config

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

Контроллер в примере кода 7 показывает, как можно применить профиль Cache1Hour к действию контроллера с помощью атрибута [OutputCache].

Пример кода 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");
        }
    }
}

При вызове действия Index(), определенного в контроллере в примере кода 7, будет возвращаться одно и то же время в течение 1 часа.

Выводы

Кэширование вывода предоставляет очень простой способ существенного повышения производительности приложений ASP.NET MVC. Из этого руководства вы узнали, как использовать атрибут [OutputCache] для кэширования выходных данных действий контроллера. Кроме того, вы узнали, как с помощью свойств атрибута [OutputCache], таких как Duration и VaryByParam, изменять способ возвращения кэшированного контента. В завершении вы узнали, как задавать профили кэширования в файле веб-конфигурации.