Поддержка кэширования для веб-служб HTTP WCF

платформа .NET Framework 4.6.1 позволяет использовать декларативный механизм кэширования, уже доступный в ASP.NET в веб-службах WCF Web HTTP. Это позволяет кэшировать ответы от операций службы WCF Web HTTP. Когда пользователь отправляет инструкцию HTTP GET службе, настроенной для кэширования, ASP.NET отправляет обратно кэшированный ответ, метод службы при этом не вызывается. По истечении срока действия кэш в следующий раз, когда пользователь отправит инструкцию HTTP GET, будет вызван метод службы и ответ будет снова кэширован. Дополнительные сведения о кэшировании ASP.NET см. в ASP.NET обзоре кэширования.

Кэширование базовой службы Web HTTP

Чтобы включить кэширование веб-службы HTTP, сначала необходимо включить ASP.NET совместимость, применив AspNetCompatibilityRequirementsAttribute его к параметру RequirementsModeAllowed службы или Required.

платформа .NET Framework 4 представляет новый атрибутAspNetCacheProfileAttribute, который позволяет указать имя профиля кэша. Этот атрибут применяется к операции службы. В следующем примере атрибут AspNetCompatibilityRequirementsAttribute применяется к службе для включения совместимости с ASP.NET, а операция GetCustomer настраивается для кэширования. Атрибут AspNetCacheProfileAttribute задает профиль кэширования, содержащий используемые параметры кэширования.

[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
public class Service
{
    [WebGet(UriTemplate = "{id}")]
    [AspNetCacheProfile("CacheFor60Seconds")]
    public Customer GetCustomer(string id)
    {
        // ...
    }
}

Кроме того, включите режим совместимости ASP.NET в файле web.config, как показано в следующем примере.

<system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>

Предупреждение

Если режим совместимости с ASP.NET не включен и используется AspNetCacheProfileAttribute, то возникнет исключение.

Имя профиля кэша, заданное атрибутом AspNetCacheProfileAttribute, идентифицирует профиль кэша, добавленный в файл конфигурации Web.config. Профиль кэша определен в элементе <outputCacheSetting> , как показано в следующем примере конфигурации.

<!-- ...  -->
<system.web>  
   <caching>  
      <outputCacheSettings>  
         <outputCacheProfiles>  
            <add name="CacheFor60Seconds" duration="60" varyByParam="none" sqlDependency="MyTestDatabase:MyTable"/>  
         </outputCacheProfiles>  
      </outputCacheSettings>  
   </caching>  
   <!-- ... -->  
</system.web>  

Этот же элемент конфигурации доступен приложениям ASP.NET. Дополнительные сведения о профилях кэша ASP.NET см. в статье OutputCacheProfile. Самыми важными атрибутами для служб Web HTTP в профиле кэша являются cacheDuration и varyByParam. Оба атрибута обязательные. cacheDuration задает время (в секундах), в течение которого ответ должен храниться в кэше. varyByParam позволяет задать параметр строки запроса, используемый для кэширования ответов. Все запросы, составленные с разными значениями параметра строки запроса, кэшируются отдельно. Например, после выполнения первоначального запроса все последующие запросы http://MyServer/MyHttpService/MyOperation?param=10, сделанные с тем же URI, будут возвращены кэшированный ответ (до тех пор, пока длительность кэша не истекла). Ответы для аналогичного запроса, имеющего другое значение для параметра строки запроса, кэшируются отдельно. Если такое раздельное кэширование нежелательно, установите параметр varyByParam в значение «none».

Зависимость кэша SQL

Ответы службы Web HTTP также могут быть кэшироваться с зависимостью от кэша SQL. Если служба WCF Web HTTP зависит от данных, которые хранятся в базе данных SQL, можно кэшировать ответ службы и аннулировать кэшированный ответ при изменении данных в таблице базы данных SQL. Это полностью настраивается в файле Web.config. Сначала определите строка подключения в элементе<connectionStrings>.

<connectionStrings>
  <add name="connectString"
       connectionString="Data Source=MyService;Initial Catalog=MyTestDatabase;Integrated Security=True"
       providerName="System.Data.SqlClient" />
</connectionStrings>

Затем необходимо включить зависимость кэша SQL в элементе <>caching><system.webвнутри элемента, как показано в следующем примере конфигурации.

<system.web>
  <caching>
    <sqlCacheDependency enabled="true" pollTime="1000">
      <databases>
        <add name="MyTestDatabase" connectionStringName="connectString" />
      </databases>
    </sqlCacheDependency>
    <!-- ... -->
  </caching>
  <!-- ... -->
</system.web>

В данном случае зависимость кэша SQL включена и задан интервал опроса в 1000 миллисекунд. Каждый раз при истечении времени опроса таблица базы данных проверяется на предмет обновлений. Если найдены изменения, то содержимое кэша удаляется, при этом при следующем вызове операции службы новый ответ будет помещен в кэш. В элементе <sqlCacheDependency> добавляются базы данных и ссылались на строка подключения в <databases> элементе, как показано в следующем примере.

<system.web>
  <caching>
    <sqlCacheDependency enabled="true" pollTime="1000">
      <databases>
        <add name="MyTestDatabase" connectionStringName="connectString" />
      </databases>  
    </sqlCacheDependency>  
    <!-- ... -->  
  </caching>  
  <!-- ... -->  
</system.web>  

Затем необходимо настроить параметры кэша выходных данных в элементе <caching> , как показано в следующем примере.

<system.web>
  <caching>  
    <!-- ...  -->
    <outputCacheSettings>
      <outputCacheProfiles>
        <add name="CacheFor60Seconds" duration="60" varyByParam="none" sqlDependency="MyTestDatabase:MyTable" />
      </outputCacheProfiles>
    </outputCacheSettings>
  </caching>
  <!-- ... -->
</system.web>

Здесь для длительности кэша задано значение 60 секунд, равное нулю, varyByParam и sqlDependency задается список с запятой с разделителями имен базы данных или таблиц, разделенных двоеточиями. При изменении данных в MyTable ответ для операции службы удаляется из кэша. При вызове операции формируется новый ответ (путем вызова операции службы), который помещается в кэш и возвращается клиенту.

Внимание

Чтобы ASP.NET получить доступ к базе данных SQL, необходимо использовать средство регистрации SQL Server ASP.NET. Помимо этого, необходимо разрешить соответствующей учетной записи пользователя доступ к базе данных и таблице. Дополнительные сведения см. в разделе "Доступ к SQL Server" из веб-приложения.

Условное кэширование на основе HTTP GET

В сценариях веб-http условный HTTP GET часто используется службами для реализации интеллектуального кэширования HTTP, как описано в спецификации HTTP. Для этого служба должна задать значение заголовка ETag в ответе HTTP. Также необходимо посмотреть заголовок If-None-Match в запросе HTTP, чтобы проверить, совпадает ли какой-либо элемент ETag с текущим элементом ETag.

Для запросов GET и HEAD метод CheckConditionalRetrieve получает значение ETag и сравнивает его с заголовком If-None-Match запроса. Если заголовок присутствует и имеется совпадение, WebFaultException создается исключение с кодом состояния HTTP 304 (не изменено), а в ответ добавляется заголовок ETag с соответствующим ETag.

Одна перегрузка метода CheckConditionalRetrieve получает последнюю измененную дату и проверяет ее относительно заголовка If-Modified-Since запроса. Если заголовок присутствует и источник не был изменен, то возникает исключение WebFaultException с кодом состояния HTTP 304 (нет изменений).

Для запросов PUT, POST и DELETE метод CheckConditionalUpdate получает текущее значение ETag источника. Если текущее значение ETag равно null, метод проверка, что заголовок If-None- Match имеет значение "*". Если текущее значение ETag не является значением по умолчанию, то метод проверяет текущее значение ETag относительно заголовка If-Match запроса. В обоих случаях метод выдает исключение WebFaultException с кодом состояния HTTP 412 (предварительное условие не выполнено), если ожидаемый заголовок не присутствует в запросе либо его значение не проходит проверку условия, и задает заголовку ETag текущее значение ETag.

Методы CheckConditional и SetETag гарантируют, что значение ETag, заданное для заголовка ответа, является допустимым значением ETag в соответствии со спецификацией протокола HTTP. Это предполагает заключение значения ETag в двойные кавычки (если они отсутствуют) и правильное экранирование символов двойных кавычек внутри строки. Нестрогое сравнение ETag не поддерживается.

В следующем примере показано использование этих методов.

[WebGet(UriTemplate = "{id}"), Description("Returns the specified customer from customers collection. Returns NotFound if there is no such customer. Supports conditional GET.")]
public Customer GetCustomer(string id)
{
    lock (writeLock)
    {
        // return NotFound if there is no item with the specified id.
        object itemEtag = customerEtags[id];
        if (itemEtag == null)
        {
            throw new WebFaultException(HttpStatusCode.NotFound);
        }
  
        // return NotModified if the client did a conditional GET and the customer item has not changed
        // since when the client last retrieved it
        WebOperationContext.Current.IncomingRequest.CheckConditionalRetrieve((long)itemEtag);
        Customer result = this.customers[id] as Customer;

        // set the customer etag before returning the result
        WebOperationContext.Current.OutgoingResponse.SetETag((long)itemEtag);
        return result;
    }
}

Соображения безопасности

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