La semana pasada os contaba en un post la forma de eliminar la caché de salida de una página a voluntad, antes de que caduque de manera "natural", usando el método RemoveOutputCacheItem. Este método tiene el problema de que debemos hacerlo desde una página diferente a la que está cacheada, porque en ésta los eventos de servidor (incluyendo el click de un botón) no saltan. Además dejé planteado un caso relativamente común que se puede dar y en el que resultaría útil disponer de otra forma de controlar si se hace caché o no.

Imagina esta situación: tienes una página en caché que los usuarios normales pueden ver pero no editar, pero que los administradores pueden cambiar usando los botones apropiados. Si simplemente activas la caché para que los usuarios vean la versión cacheada de la página, como hemos visto, ninguno de los eventos normales saltará, así que los administradores tampoco podrán editar nada, ni tampoco se verán los cambios que han hecho hasta que la caché se refresque. ¿Cómo podemos conseguir que la caché salte únicamente para los usuarios normales, pero que para los editores o administradores la página funcione con normalidad?

La primera tentación es pensar en hacer caché de la página con un VaryByCustom, que guarde en caché una versión diferente de la salida página según quien sea el usuario actualmente autenticado en el sistema. Esto está bien excepto por un pequeño problema: que aunque sí serviría para crear una versión diferente en caché de la página por cada tipo de usuario (o el criterio que elijamos), no nos permite no hacer caché. Es decir, no podemos anularla por ejemplo para los administradores y hacerla para el resto de perfiles, que es lo que buscábamos. Así que este camino es un punto muerto.

La técnica para conseguirlo pasa por el uso de un evento especial de la caché que es llamado automáticamente una vez en cada petición. Para ello disponemos del método AddValidationCallback. Éste toma como argumentos la referencia a un delegado del tipo HttpCacheValidateHandler, y un objeto cualquiera que sirve para pasar los datos que necesitemos a la llamada (normalmente lo usaremos con una referencia nula).

Así, en el evento de carga de la página pondremos este código:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    lblHora.Text = DateTime.Now.ToLongTimeString()
    If User.Identity.IsAuthenticated Then
       
Response.Cache.AddValidationCallback(New HttpCacheValidateHandler(AddressOf ValidateCache), Nothing)
    End If
End Sub

El evento Load saltará siempre que la página no esté en Caché, es decir, la primera vez que llamemos a la página. En este caso lo que hacemos es que si el usuario actual está autenticado, asignamos la función ValidateCache como la responsable de decidir en cada petición la validez de la caché actual de la página.

Ahora sólo resta definir el manejador del evento, que será como este:

Public Shared Sub ValidateCache(ByVal Currentcontext As HttpContext, ByVal data As Object, _
ByRef status As HttpValidationStatus)

    If
Currentcontext.User.IsInRole("Administradores") Then
        status = HttpValidationStatus.IgnoreThisRequest
    Else
        status = HttpValidationStatus.Valid
    End If

End Sub

Lo que se hace es comprobar si el usuario actual está dentro del rol de "Administradores", en cuyo casi devolvemos como estátus de validación de caché el valor IgnoreThisRequest de la enumeración HttpValidationStatus. Al hacer esto lo que conseguimos es que se obvie la caché para la página, y por lo tanto ésta se ejecute normalmente.

Si el usuario es cualquier otro tipo de usuario validamos la caché (con el valor Valid de la misma enumeración) de forma que ésta actuará de la manera habitual.

Con esto conseguimos el efecto buscado que es que la caché se realice para todos los usuarios excepto para los administradores, de foram que puedan trabajar en ella normalmente.

En un programa real iríamos un paso más allá e invalidaríamos la caché de la página con la técnica aprendida en el post anterior siempre que un administrador cambiara algún dato en la página, dando acceso inmediato a la nueva versión a todos los usuarios.

Aunque antes he dicho que cuando la página está en caché los eventos de ésta no saltan, éste evento de validación saltará siempre (en todas las peticiones) porque no es se trata de un evento de la página, sino un evento de la propia caché, y por lo tanto ajeno al ciclo de vido de la página (pero no al ciclo de vida de una petición). No te dejes engañar por el hecho de que el código del evento esté en la propia página.

Una sutileza más

Antes de dar por terminado el post. La enumeración HttpValidationStatus aún tiene un valor más aparte de los dos que acabamos de ver. Se trata del valor HttpValidationStatus.Invalid. Si utilizamos este valor en lugar de HttpValidationStatus.IgnoreThisRequest, como en el ejemplo, aparentemente la técnica funcionará igual, pues se saltará la caché para los administradores y permitirá que estos trabajen en la página con todos sus eventos. 

Sin embargo hay una diferencia importante: cuando usamos HttpValidationStatus.IgnoreThisRequest lo que conseguimos es saltarnos el uso de la caché, de forma que la página se ejecuta normalmente y la caché existente no se ve afectada, quedando intacta para el resto de los usuarios. Sin embargo cuando usamos el valor HttpValidationStatus.Invalid lo que indicamos es que la caché actual ya no vale (lo que técnicamente se conoce como "fallo de caché" o "cahé miss" e inglés), y por lo tanto el efecto conseguido es que se elimina la caché que existiera en ese momento para la página, se ejecuta ésta y se genera una nueva versión cacheada de la misma.

La diferencia es tremenda. En el primer caso nos la saltamos sin afectarla, pero en el segundo lo que hacemos es generar una versión nueva para todos los usuarios, con las implicaciones de rendimiento y posible visualización de información consistente que ello implica. Así que hay que tenerlo en cuenta.

Obviamente podemo usar HttpValidationStatus.Invalid para borrar la caché en casos que realmente así lo requieran, pero hay que tener en cuenta los posibles efectos.

Este tema está realmente pobremente documentado y es muy poco conocido, así que espero que te resulte de utilidad :-)

💪🏻 ¿Este post te ha ayudado?, ¿has aprendido algo nuevo?
Pues NO te pido que me invites a un café... Te pido algo más fácil y mucho mejor