in ASP.NET

Cache + .NET: OutputCache

Como eu disse em um post anterior (Cache + .NET: Cache de Objetos), existem diversas técnicas para conseguirmos melhorar a performance de nossas aplicações web. Seguindo a série de 4 posts que eu estou escrevendo sobre as diversas técnicas para utilizar Cache com .NET e aplicações Web, vou abordar temas como: Cache de Objetos, OutputCache, Sistemas de Cache Distribuídos e Cache Http.

Neste segundo post vou abordar o OutputCache.

O OutputCache é sem dúvida uma das melhores maneiras de aumentarmos a performance em nossas aplicações, ele vem evoluindo e existe desde o ASP.NET 1.1, com ele é possível manter em memória (ou em outro meio) uma versão gerada daquela página, user control, action ou partial, tendo diversos mecanismos de controles, como o tempo de duração do cache e a variação e versionamento de acordo com alguns parâmetros (querystring, paginas, ids, etc).

OutputCache

Abaixo tem um fluxo de como funciona o OutputCache:

outputcache

ASP.NET WebForms

No ASP.NET WebForms utilizamos uma diretiva de página para configurar o OutputCache, no exemplo abaixo, a default.aspx possui um outputcache de 300 segundos, e não varia por nenhum parâmetro.

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>

<%@ OutputCache Duration="300" VaryByParam="none" %>

<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">

    <div class="hero-unit">
        <h1>ASP.NET <%=DateTime.Now.ToLongTimeString() %></h1>
        <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS, and JavaScript.</p>
        <p><a href="http://www.asp.net" class="btn btn-primary btn-large">Learn more &raquo;</a></p>
    </div>
</asp:Content>

Imaginando que estivéssemos em uma pagina que vária de acordo com o id do produto (produto.aspx?produtoid=123), poderíamos configurar da seguinte forma:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Produto.aspx.cs" Inherits="WebApplication1.Produto" %>
<%@ OutputCache Duration="300" VaryByParam="produtoId" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Produto</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    </div>
    </form>
</body>
</html>

Com esta configuração, utilizando o Atributo VarayByParam, ele irá gerar uma versão para cada id do produto, com isso, temos a página e o cache variando de acordo com o produto que está sendo visualizado.

Além de configurar o OutputCache em uma Pagina, também é possível configurar ele em um User Control, sendo bastante útil em casos de controles que são compartilhados em diversas paginas, como por exemplo em menus ou listas de itens:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="UserControlDemo.ascx.cs" Inherits="WebApplication1.UserControlDemo" %>
<%@ OutputCache Duration="500" VaryByParam="none" %>

<div>
    <h3>UseControl Demo</h3>
</div>

 

ASP.NET MVC

Para utilizar o OutputCache no ASP.NET MVC é preciso adicionar o atributo OutputCache em cada Action ou no Controller, no exemplo abaixo eu configurado o cache por 300 segundos sem variação na Action Index do Controller Home:

public class HomeController : Controller
{
    [OutputCache(Duration=300, VaryByParam="none")]
    public ActionResult Index()
    {
        return View();
    }
}

É possível configurar o OutputCache no nível do controller todo, como no exemplo abaixo:

[OutputCache(Duration = 300, VaryByParam = "none")]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";

        return View();
    }

    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}

No ASP.NET MVC também é possível variar o OutputCache de diversas maneiras, como por exemplo variar de acordo com o Id:

[OutputCache(Duration = 300, VaryByParam = "id")]
public ActionResult DetalheProduto(int id)
{
    return View();
}

Atributos

O OutputCache possui diversos atributos que podem ser configurados, entre os principais estão o Duration, e o VaryByParam, abaixo eu busquei detalhar os principais atributos que podem ser configurados.

Duration

O tempo em segundos que a pagina ou controle será mantido no cache.

Location

Em quais locais o cache será localizado, por padrão o valor default é o Any, entretanto é possível configurar o cache somente no lado server, ou somente no lado do navegador(detalhes: OutputCacheLocation Enumeration).

*não suportado em usercontrols

CacheProfile

O nome das configurações de cache que estão associadas com aquela pagina, em geral elas ficam no web.config, com estas configurações, é possível alterar os valores e configurações do OutputCache, sem recompilar e aplicação, somente alterando as configurações no web.config.

Action:

public class HomeController : Controller
{
    [OutputCache(CacheProfile="HomePage")]
    public ActionResult Index()
    {
        return View();
    }
}

Web.Config:

<system.web>
  <caching>
    <outputCacheSettings>
      <outputCacheProfiles>
        <add name="HomePage" duration="3600" varyByParam="None" location="ServerAndClient"/>
      </outputCacheProfiles>
    </outputCacheSettings>
  </caching>
</system.web>

*não suportado em usercontrols, e caso a chave com o nome não exista no arquivo de configuração, uma exceção será disparada.

NoStore

Esta configuração é definida como false por padrão, ela configurada como true a resposta daquela pagina ou action é adicionada um header especifico para evitar o cache de informações sensíveis no navegador ou em um proxy de internet.

image

Shared

Atributo que é utilizado em user controls para definir se o cache do controle pode ser compartilhado entre diversas páginas (containers que utilizam o controle).

*não é suportado na configuração de cache de uma página ou action

SqlDependency

String que identifica o nome do banco de dados e a tabela da qual aquele cache depende, em caso de atualizações na tabela, um sistema de notificação do SQL Server (2005) notifica a aplicação que aquele cache expirou. (maiores detalhes)

*valido somente em .aspx

VaryByCustom

O VarayByCustom é um atributo muito útil, com ele é possível implementarmos a nossa própria regra de variação do cache. Para isso precisaremos criar um override do GetVaryByCustomString no Global.asax de nossa aplicação, e ao utilizar o VarayByCustom, ele irá utilizar este método para retornar uma chave de variação de acordo com os parâmetros e regras que definirmos:

Ex:

HomeController.cs

public class HomeController : Controller
{
    [OutputCache(Duration=300, VaryByCustom="VaricaoCustomDemo")]
    public ActionResult Index()
    {
        ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

        return View();
    }
}

Global.asax

public class MvcApplication : System.Web.HttpApplication
{
    public override string GetVaryByCustomString(HttpContext context, string custom)
    {
        if (custom == "VaricaoCustomDemo")
            return "ChaveDoCache"; //TODO:Regra da variação

        return base.GetVaryByCustomString(context, custom);
    }
VaryByHeader

Com este parâmetro é possível configurar a variação do OutputCache de acordo com os headers da  requisição, nele é possível configurar separado por ponto e virgula, ex:

public class HomeController : Controller
{
    [OutputCache(Duration=300, VaryByHeader="header1;header2")]
    public ActionResult Index()
    {
        ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

        return View();
    }
}
VaryByParam

Esta parâmetro é um dos principais, é nele que podemos configurar a variação básica do OutputCache, como por exemplo parâmetros de querystring, parâmetros de roteamento, ou campos no POST, caso haja vários parâmetros podemos utilizar eles sendo separados por ponto e virgula, ou caso não seja necessário que o cache varie de acordo com os parâmetros, podemos utilizar o valor: “none”, ou caso seja necessário que ele varie por todos os parâmetros podemos utilizar o asterisco (*).

exemplo de variação por id:

[OutputCache(Duration=300, VaryByParam="id")]
public ActionResult Detalhe(int id)
{
    ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

    return View();
}

exemplo de variação por id e pagina:

[OutputCache(Duration=300, VaryByParam="id;pagina")]
public ActionResult Detalhe(int id)
{
    ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

    return View();
}

exemplo de sem variação:

[OutputCache(Duration=300, VaryByParam="none")]
public ActionResult Detalhe(int id)
{
    ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

    return View();
}

exemplo de variação por qualquer parâmetro:

[OutputCache(Duration=300, VaryByParam="*")]
public ActionResult Detalhe(int id)
{
    ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

    return View();
}
VaryByControl

Atributo no qual podemos configurar os ids (separados por ponto e virgula) dos controles servers pelo os quais o OutputCache deve variar.

VaryByContentEncoding

Atributo que define a variação do OutputCache de acordo com o Content Encoding, ele é utilizado em conjunto com o header Accept-Encoding. Como os principais atributos, o VaryByContentEncoding é configurado como uma lista de encodings separados por ponto e virgula.

ex:

[OutputCache(Duration=300, VaryByContentEncoding="gzip")]
public ActionResult Index()
{
    return View();
}

 

Extensibilidade

Desde o .NET 4.0 o ASP.NET tornou possível a extensibilidade do OutputCache através de providers, neste post (ASP.NET OutputCache usando Memcached) eu demonstro como criar um provider e armazenar o OutputCache no Memcached.

Isto pode ser bem útil em cenários de grande escala, onde é possível crescer horizontalmente a infraestrutura de cache, e tirando a responsabilidade de manter as informações do OutputCache na memória dos servidores web.

Dicas

Para finalizar o post, quero deixar 4 dicas que eu considero importante:

  1. Sempre pense em uma estratégia para utilizar OutputCache, não deixe para implementar o cache após desenvolver a aplicação, faz parte da construção de uma app o pensamento em estratégias e implementações de cache.
  2. Nunca desenvolva com o OutputCache desabilitado (ou tenha a total consciência disso), é possível desabilitar o OutputCache no Web.Config, entretanto isso pode ser problemático, as vezes esquecemos de uma variação importante do OutputCache, e como estamos com ele desabilitado no ambiente de desenvolvimento, não percebemos ou mascaramos o problema.
  3. Tome cuidado com ambientes autenticados, o uso do OutputCache é totalmente possível em ambientes autenticados e logados, entretanto é preciso definir uma estratégia de variação, e o seu uso deve ser feito em cenários que faça sentido, e com muito cuidado, para uma versão do cache do usuário A não aparecer em produção para o usuário B.
  4. Utilize o Cache Profile, esta é uma forma muito boa de centralizar e controlar as configurações do OutputCache no web.config, entretanto em algumas versões, o OutputCache com Cache Profile não é suportado em ChildActions.

Espero que este post seja útil, estou a disposição para qualquer dúvida, critica ou sugestão.

Abs

Rodolfo

  • Luís Gabriel Nascimento Simas

    Muito bom. Valeu mesmo

  • Antônio Filho

    Olá, primeiramente parabéns! Comecei a estudar suas utilizações a pouco tempo, devido a uma utilização em um sistema corporativo. Estou com uma dúvida e talvez possa possa matá-la de maneira bem rápida: Estou utilizando o OutputCache para carregar todos os menus que um usuário (logado) terá acesso. Porém, quero que a cada login ele recarregue novamente esse método (action), independentemente se o tempo (duration) esgotou ou não… Abraços!

  • http://www.rodolfofadino.com.br/ Rodolfo Fadino

    Boa noite Antônio, muito obrigado.
    Acredito que você precise implementar um VaryByCustom especifico para esta regra, e concatenar na string de versão do cache, em qual role aquele seu usuário se encaixa, mas eu tentaria deixar poucas versões no cache de menu, e não um versão para cada usuário, como as informacoes do menu variam de acordo com o usuario?

  • Tiago Trindade

    Sensacional Rodolfo. Utilizei seu post como uma das fontes pra dar um treinamento sobre mvc. =]
    Abraço