Cache com suporte para serviços HTTP Web do WCF

O .NET Framework 4.6.1 permite usar o mecanismo de cache declarativo já disponível em ASP.NET nos serviços HTTP da Web do WCF. Isso permite armazenar em cache as respostas das operações de serviço HTTP da Web do WCF. Quando um usuário envia um HTTP GET ao seu serviço configurado para armazenar em cache, o ASP.NET envia a resposta armazenada em cache novamente e o método de serviço não é chamado. Quando o cache expira, na próxima vez que um usuário enviar um HTTP GET, o método de serviço será chamado e a resposta será novamente armazenada em cache. Para obter mais informações sobre o cache do ASP.NET, consulte Visão geral do cache do ASP.NET.

Cache básico do serviço HTTP web

Para habilitar o cache do serviço HTTP WEB, primeiro você precisa habilitar a compatibilidade do ASP.NET aplicando o AspNetCompatibilityRequirementsAttribute à configuração do serviço RequirementsMode para Allowed ou Required.

O .NET Framework 4 apresenta um novo atributo chamado de AspNetCacheProfileAttribute, que permite especificar um nome de perfil de cache. Esse atributo é aplicado a uma operação de serviço. O exemplo a seguir aplica AspNetCompatibilityRequirementsAttribute a um serviço para habilitar a compatibilidade do ASP.NET e configura a operação GetCustomer para cache. O atributo AspNetCacheProfileAttribute especifica um perfil de cache que contém as configurações de cache a serem usadas.

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

Ative também o modo de compatibilidade ASP.NET no arquivo Web.config, conforme mostrado no exemplo a seguir.

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

Aviso

Se o modo de compatibilidade ASP.NET não estiver ativado e AspNetCacheProfileAttribute for usado, uma exceção será iniciada.

O nome do perfil de cache especificado por AspNetCacheProfileAttribute identifica um perfil de cache adicionado ao arquivo de configuração Web.config. O perfil de cache é definido em um elemento <outputCacheSetting>, conforme mostrado no exemplo de configuração a seguir.

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

Esse é o mesmo elemento de configuração disponível para aplicativos ASP.NET. Para obter mais informações sobre perfis de cache do ASP.NET, confira OutputCacheProfile. Para serviços HTTP Web, os atributos mais importantes no perfil de cache são: cacheDuration e varyByParam. Ambos os atributos são necessários. cacheDuration define a quantidade de tempo em que uma resposta deve ser armazenada em cache em segundos. varyByParam permite especificar um parâmetro de cadeia de caracteres de consulta usado para armazenar respostas em cache. Todas as solicitações feitas com valores de parâmetro de cadeia de caracteres de consulta diferentes são armazenadas em cache separadamente. Por exemplo, depois que uma solicitação inicial for feita para http://MyServer/MyHttpService/MyOperation?param=10, todas as solicitações subsequentes feitas com o mesmo URI retornarão a resposta armazenada em cache (desde que a duração do cache não tenha decorrido). As respostas para uma solicitação semelhante que é a mesma, mas que tem um valor diferente para o parâmetro de cadeia de caracteres de consulta de parâmetro, são armazenadas em cache separadamente. Se você não quiser esse comportamento de armazenamento em cache separado, defina varyByParam como "nenhum".

Dependência do cache SQL

As respostas do serviço HTTP Web também podem ser armazenadas em cache com uma dependência de cache SQL. Se o serviço HTTP da Web do WCF depender dos dados armazenados em um banco de dados SQL, talvez você queira armazenar em cache a resposta do serviço e invalidar a resposta armazenada em cache quando os dados na tabela do banco de dados SQL forem alterados. Esse comportamento é configurado completamente dentro do arquivo Web.config. Primeiro, defina uma cadeia de conexão no elemento <connectionStrings>.

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

Em seguida, você precisa habilitar a dependência de cache SQL dentro de um elemento <caching> dentro do elemento <system.web>, conforme mostrado no exemplo de configuração a seguir.

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

Aqui, a dependência do cache SQL está habilitada e um tempo de sondagem de 1000 milissegundos é definido. Cada vez que o tempo de sondagem decorrido, a tabela de banco de dados é verificada quanto a atualizações. Se forem detectadas alterações, o conteúdo do cache será removido e, na próxima vez que a operação de serviço for invocada, uma nova resposta será armazenada em cache. No elemento <sqlCacheDependency>, adicione os bancos de dados e faça referência às cadeias de conexão dentro do elemento <databases>, conforme mostrado no exemplo a seguir.

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

Em seguida, você precisa definir as configurações de cache de saída no elemento <caching>, conforme mostrado no exemplo a seguir.

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

Aqui, a duração do cache é definida como 60 segundos, varyByParam é definido como nenhum e sqlDependency é definido como uma lista delimitada por ponto e vírgula de pares de nome/tabela de banco de dados separados por dois pontos. Quando os dados em MyTable são alterados, a resposta armazenada em cache para a operação de serviço é removida e quando a operação é invocada uma nova resposta é gerada (chamando a operação de serviço), armazenada em cache e retornada ao cliente.

Importante

Para o ASP.NET acessar um banco de dados SQL, você precisa usar a Ferramenta de registro do SQL Server do ASP.NET. Além disso, você precisa permitir que a conta de usuário apropriada acesse o banco de dados e a tabela. Para obter mais informações, consulte Acessando o SQL Server de um aplicativo Web.

Cache baseado em HTTP GET condicional

Em cenários HTTP Web, um HTTP GET condicional é geralmente usado pelos serviços para implementar o cache inteligente de HTTP, conforme descrito na Especificação de HTTP. Para fazer isso, o serviço precisa definir o valor do cabeçalho ETag na resposta HTTP. Ele também precisa verificar o cabeçalho If-None-Match na solicitação HTTP para ver se alguma das ETag especificadas corresponde à ETag atual.

Para solicitações GET e HEAD, CheckConditionalRetrieve pega um valor ETag e verifica-o no cabeçalho If-None-Match da solicitação. Se o cabeçalho estiver presente e houver uma correspondência, um WebFaultException com um código de status HTTP 304 (Não Modificado) será gerado e um cabeçalho ETag será adicionado à resposta com a ETag correspondente.

Uma sobrecarga do método CheckConditionalRetrieve usa uma última data modificada e verifica-a no cabeçalho If-Modified-Since da solicitação. Se o cabeçalho estiver presente e o recurso não tiver sido modificado desde então, um WebFaultException com um código de status HTTP 304 (Não Modificado) será gerado.

Para solicitações PUT, POST e DELETE, CheckConditionalUpdate usa o valor ETag atual de um recurso. Se o valor de ETag atual for nulo, o método verificará se o cabeçalho If-None-Match tem um valor de "*". Se o valor atual da ETag não for um valor padrão, o método verificará o valor ETag atual em relação ao cabeçalho If-Match da solicitação. Em ambos os casos, o método gera um WebFaultException com um código de status HTTP 412 (Falha na pré-condição) se o cabeçalho esperado não estiver presente na solicitação ou seu valor não atender à verificação condicional e definir o cabeçalho ETag da resposta para o valor ETag atual.

Os métodos CheckConditional e o método SetETag garantem que o valor ETag definido no cabeçalho de resposta seja uma ETag válida de acordo com a especificação HTTP. Isso inclui cercar o valor de ETag entre aspas duplas se elas ainda não estiverem presentes e escaparem corretamente de caracteres de aspas duplas internas. Não há compatibilidade com comparaçoes Etag.

O exemplo a seguir mostra como usar estes métodos.

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

Considerações de segurança

As solicitações que exigem autorização não devem ter respostas armazenadas em cache, pois a autorização não é executada quando a resposta é atendida do cache. O cache dessas respostas introduziria uma grave vulnerabilidade de segurança. Normalmente, as solicitações que exigem autorização fornecem dados específicos do usuário e, portanto, o cache do lado do servidor nem é benéfico. Nessas situações, será mais apropriado armazenar em cache do lado do cliente ou simplesmente não armazenar em cache.