Aproveite o cache do navegador no IIS (questão do google pagespeed)

Existem várias questões sobre como aproveitar o cache do navegador, mas não encontrei nada útil para como fazer isso em um aplicativo ASP.NET. O Pagespeed do Google diz que esse é o maior problema de desempenho. Até agora eu fiz isso no meu web.config :

  <!-- -->    

O código comentado funciona. Eu posso configurar o header expirar para um determinado momento no futuro, mas não consegui definir o cacheControlMaxAge para definir quantos dias a partir de agora o conteúdo estático seria armazenado em cache. Não funciona. Minhas perguntas são:

Como eu posso fazer isso? Eu sei que é possível definir o cache apenas para pasta específica que seria uma boa solução, mas não está funcionando também. O aplicativo está hospedado no Windows Server 2012, no IIS8, o pool de aplicativos está definido como clássico.

Depois de definir esse código na configuração da web, obtive a velocidade de páginas de 72 (antes era 71). 50 arquivos não foram armazenados em cache. (Agora 49) Eu estava me perguntando por que e acabei de perceber que um arquivo foi realmente armazenado em cache (arquivo svg). Infelizmente png e jpg não foram. Este é meu web.config

    
"

EDIT: Se eu definir data exata de expiração, o cache está funcionando, mas não para jpg, gif …. apenas para png

EDIT2: Se eu definir cacheControlCustom="public" como aqui:

  

o cache está funcionando, mas ainda não para jpegs e gifs; funciona apenas para svgs e pngs.

A maioria dos problemas de cache do navegador pode ser resolvida visualizando os headers de resposta (isso pode ser feito nas ferramentas de desenvolvedor do Google Chrome).

insira a descrição da imagem aqui

Agora, a seção clientCache do seu arquivo web.config deve definir o cache de saída para uma idade máxima, como você vê na imagem abaixo definiu o max-age para 86400 que é de 1 dia em segundos.

Aqui está o snippet web.config para esta configuração.

  

Agora isso é ótimo, o header de resposta tem uma propriedade max-age definida no header Cache-Control . Portanto, o navegador deve estar armazenando o conteúdo em cache. Bem, isso é verdade, mas alguns navegadores exigem outro sinalizador para ser definido. Especificamente, o sinalizador public definido para o header de controle de cache. Isso pode ser facilmente adicionado usando o atributo cacheControlCustom no web.config . Aqui está um exemplo.

  

Agora, quando nós repetimos a página e inspecionamos os headers.

insira a descrição da imagem aqui

Agora, como você pode ver na imagem acima, agora temos o valor public, max-age=86400 . Portanto, nosso navegador tem tudo o que precisa para armazenar os resources em cache. Agora, examinar os headers e a guia “Rede” do Google Chrome nos ajudará.

Aqui está o primeiro pedido para o arquivo .. Observe que o arquivo não está em cache … insira a descrição da imagem aqui

Agora vamos navegar de volta para esta página ( NOTA: não atualize a página, falaremos sobre isso em um segundo). Você verá a resposta retornando agora do cache (conforme circulado).

insira a descrição da imagem aqui

Agora, o que acontece se eu atualizar a página usando F5 ou usando o recurso de atualização do navegador. Espere .. onde foi o (from cache) . insira a descrição da imagem aqui

Bem no Google Chrome (não tenho certeza sobre outros navegadores) usando o botão de atualização irá baixar novamente os resources estáticos, independentemente do header do cache ( inserir esclarecimento aqui por favor ). Isso significa que os resources foram recuperados novamente e o header de idade máxima foi enviado.

Agora, depois de toda a explicação acima, certifique-se de testar como você está monitorando os headers de cache.

Atualizar

Com base nos seus comentários, você afirmou ter um manipulador genérico ( IHttpHandler ) chamado Image.ashx com o tipo de conteúdo image/jpg . Agora você pode esperar que o comportamento padrão seria armazenar em cache esse manipulador. No entanto, o IIS vê a extensão .ashx (corretamente) como um script dynamic e não está sujeita ao armazenamento em cache sem definir explicitamente os headers de cache no próprio código.

Agora, é aqui que você precisa ser cuidadoso, pois normalmente os IHttpHandlers não devem ser armazenados em cache, pois normalmente fornecem conteúdo dynamic. Agora, se é improvável que esse conteúdo mude, você pode definir seus headers de cache diretamente no código. Aqui está um exemplo de configuração de headers de cache em IHttpHandlers usando o contexto de Response .

 context.Response.ContentType = "image/jpg"; context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1)); context.Response.Cache.SetCacheability(HttpCacheability.Public); context.Response.Cache.SetSlidingExpiration(true); context.Response.TransmitFile(context.Server.MapPath("~/out.jpg")); 

Agora, olhando para o código, estamos definindo algumas propriedades na propriedade Cache . Para obter a resposta desejada, defini as propriedades.

  • context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1)); informa ao cache de colocação para definir a parte max-age= do header Cache-Control para 1 dia no futuro (86400 segundos).
  • context.Response.Cache.SetCacheability(HttpCacheability.Public); informa ao cache de saída para definir o header Cache-Control para public . Isso é muito importante, pois informa ao navegador para armazenar em cache o object.
  • context.Response.Cache.SetSlidingExpiration(true); informa ao cache de saída para assegurar que está configurando a parte max-age= do header Cache-Control corretamente. Sem definir a expiração de deslizamento, o cache de saída do IIS ignorará o header de idade máxima. Colocar isso em conjunto me dá esse resultado.

cache de saída do arquivo ashx

Como afirmei acima, talvez você não queira armazenar em cache os arquivos .ashx , pois normalmente eles fornecem conteúdo dynamic. No entanto, se esse conteúdo dynamic não for alterado em um determinado período, você poderá usar os methods acima para entregar seu arquivo .ashx .

Agora, em conjunto com o processo listado acima, você também pode definir o componente ETag (ver wiki) dos headers de cache para que o navegador possa verificar o conteúdo sendo entregue por uma string personalizada. O wiki afirma:

Um ETag é um identificador opaco atribuído por um servidor da Web a uma versão específica de um recurso encontrado em um URL. Se o conteúdo do recurso nesse URL for alterado, um ETag novo e diferente será atribuído.

Portanto, esse é realmente um tipo de identificação exclusiva do navegador para identificar o conteúdo que está sendo entregue na resposta. Ao fornecer esse header, o navegador no próximo recarregamento enviará um header If-None-Match com o ETag da última resposta. Podemos modificar nosso manipulador para detectar o header If-None-Match e compará-lo ao nosso próprio Etag gerado. Agora não há ciência exata para gerar ETags mas uma boa regra é entregar um identificador que provavelmente irá definir apenas uma entidade. Neste caso eu gosto de usar duas cordas concatenadas juntas como.

 System.IO.FileInfo file = new System.IO.FileInfo(context.Server.MapPath("~/saveNew.png")); string eTag = file.Name.GetHashCode().ToString() + file.LastWriteTimeUtc.Ticks.GetHashCode().ToString(); 

No snippet acima, estamos carregando um arquivo do nosso sistema de arquivos (você pode obter isso de qualquer lugar). Eu estou usando o método GetHashCode() (em todos os objects) para obter o código de hash inteiro do object. No exemplo concordo o hash do nome do arquivo e, em seguida, a última data de gravação. A razão para a última data de gravação é que, caso o arquivo seja alterado, o código hash também é alterado, tornando as impressões digitais diferentes.

Isso gerará um código de hash semelhante a 306894467-210133036 .

Então, como usamos isso em nosso manipulador? Abaixo está a versão recém-modificada do manipulador.

 System.IO.FileInfo file = new System.IO.FileInfo(context.Server.MapPath("~/out.png")); string eTag = file.Name.GetHashCode().ToString() + file.LastWriteTimeUtc.Ticks.GetHashCode().ToString(); var browserETag = context.Request.Headers["If-None-Match"]; context.Response.ClearHeaders(); if(browserETag == eTag) { context.Response.Status = "304 Not Modified"; context.Response.End(); return; } context.Response.ContentType = "image/jpg"; context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1)); context.Response.Cache.SetCacheability(HttpCacheability.Public); context.Response.Cache.SetSlidingExpiration(true); context.Response.Cache.SetETag(eTag); context.Response.TransmitFile(file.FullName); 

Como você pode ver, mudei bastante do handler, mas você notará que nós geramos o hash Etag , verificamos se há um header If-None-Match . Se o etag hash e o header forem iguais, informamos ao navegador que o conteúdo não foi alterado, retornando o código de status 304 Not Modified .

Em seguida foi o mesmo manipulador, exceto que adicionamos o header ETag chamando:

 context.Response.Cache.SetETag(eTag); 

Quando executamos isso no navegador, recebemos.

Controle de Cache com ETag

Você verá a partir da imagem (como eu mudei o nome do arquivo) que agora temos todos os componentes do nosso sistema de cache no lugar. O ETag está sendo entregue como um header, e o navegador está enviando o header da solicitação If-None-Match para que nosso manipulador possa responder de acordo com o arquivo de cache alterado.

Usa isto. Isso funciona para mim.

    
         

Usando o acima, os arquivos de conteúdo estático serão armazenados em cache por 10 dias no navegador. Informações detalhadas sobre o elemento podem ser encontradas aqui .

Você também pode usar o elemento para definir as configurações de cache para um arquivo específico: