Soporte de almacenamiento en memoria caché para servicios Web HTTP de WCF
.NET Framework 4.6.1 le permite usar el mecanismo de almacenamiento en caché declarativo que ya está disponible en ASP.NET en los servicios Web HTTP de WCF. Este mecanismo le permite almacenar en memoria caché las respuestas de las operaciones de servicio Web HTTP de WCF. Cuando un usuario envía un protocolo HTTP GET
al servicio que está configurado para almacenarlo en memoria caché, ASP.NET devuelve la respuesta almacenada en memoria caché y no se llama al método de servicio. Cuando la memoria caché expira, la próxima vez que un usuario envía un protocolo HTTP GET
, se llama al método de servicio y la respuesta se vuelve a almacenar en memoria caché. Para más información sobre el almacenamiento en caché de ASP.NET Core, consulte Introducción al almacenamiento en caché de ASP.NET.
Almacenamiento en memoria caché básico del servicio Web HTTP
Para habilitar el almacenamiento en caché del servicio Web HTTP, debe habilitar primero la compatibilidad de ASP.NET aplicando AspNetCompatibilityRequirementsAttribute al servicio y estableciendo RequirementsMode en Allowed o Required.
.NET Framework 4 introduce un nuevo atributo llamado AspNetCacheProfileAttribute que le permite especificar un nombre de perfil de caché. Este atributo se aplica a una operación del servicio. El siguiente ejemplo aplica AspNetCompatibilityRequirementsAttribute a un servicio para habilitar la compatibilidad de ASP.NET y configura la operación GetCustomer
para almacenar en memoria caché. El atributo AspNetCacheProfileAttribute especifica un perfil de memoria caché que contiene la configuración de memoria caché que se va a utilizar.
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
public class Service
{
[WebGet(UriTemplate = "{id}")]
[AspNetCacheProfile("CacheFor60Seconds")]
public Customer GetCustomer(string id)
{
// ...
}
}
También debe activar el modo de compatibilidad de ASP.NET en el archivo Web.config, como se muestra en el siguiente ejemplo.
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
Advertencia
Si no se activa el modo de compatibilidad de ASP.NET y se utiliza AspNetCacheProfileAttribute, se produce una excepción.
El nombre de perfil de memoria caché especificado por AspNetCacheProfileAttribute identifica un perfil de memoria caché agregado al archivo de configuración Web.config. El perfil de caché se define con un elemento <outputCacheSetting>
, como se muestra en el siguiente ejemplo de configuración.
<!-- ... -->
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="CacheFor60Seconds" duration="60" varyByParam="none" sqlDependency="MyTestDatabase:MyTable"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
<!-- ... -->
</system.web>
Éste es el mismo elemento de configuración que se encuentra disponible para las aplicaciones ASP.NET. Para más información sobre los perfiles de caché de ASP.NET, consulte OutputCacheProfile. Para los servicios Web HTTP, los atributos más importantes del perfil de la memoria caché son: cacheDuration
y varyByParam
. Se necesitan ambos atributos. cacheDuration
establece la cantidad de tiempo que una respuesta se debe almacenar en memoria caché en segundos. varyByParam
permite especificar un parámetro de cadena de consulta que se usa para almacenar en memoria caché las respuestas. Todas las solicitudes realizadas con valores de parámetro de cadena de consulta diferentes se almacenan en memoria caché por separado. Por ejemplo, una vez que se realiza una solicitud inicial a http://MyServer/MyHttpService/MyOperation?param=10
, a todas las solicitudes subsiguientes realizadas con el mismo URI se les devolverá la respuesta almacenada en caché (siempre y cuando la duración del almacenamiento en caché no haya transcurrido). Las respuestas para una solicitud similar que es igual pero tiene un valor diferente para el parámetro de cadena de consulta de parámetros se almacenan en la memoria caché por separado. Si no desea este comportamiento de almacenamiento en caché por separado, establezca varyByParam
en "none".
Dependencia de memoria caché de SQL
Las respuestas del servicio Web HTTP también pueden estar almacenadas en memoria caché con una dependencia de memoria caché de SQL. Si el servicio Web HTTP de WCF depende de los datos almacenados en una base de datos SQL, puede que desee almacenar en memoria caché la respuesta del servicio e invalidar la respuesta almacenada en memoria caché cuando se produzcan cambios en los datos de tabla de base de datos SQL. Este comportamiento se configura completamente en el archivo Web.config. Debe definir primero una cadena de conexión en el elemento <connectionStrings>
.
<connectionStrings>
<add name="connectString"
connectionString="..."
providerName="System.Data.SqlClient" />
</connectionStrings>
A continuación, habilite la dependencia de memoria caché de SQL dentro de un <caching>
elemento dentro del <system.web>
elemento, como se muestra en el siguiente ejemplo de configuración.
<system.web>
<caching>
<sqlCacheDependency enabled="true" pollTime="1000">
<databases>
<add name="MyTestDatabase" connectionStringName="connectString" />
</databases>
</sqlCacheDependency>
<!-- ... -->
</caching>
<!-- ... -->
</system.web>
Aquí, la dependencia de memoria caché de SQL está habilitada y se establece un tiempo de sondeo de 1000 milisegundos. Cada vez que el tiempo de sondeo transcurre, se comprueba la tabla de la base de datos para detectar actualizaciones. Si se detectan cambios, se quita el contenido de la memoria caché y la próxima vez que se invoque la operación del servicio, se almacenará en memoria caché una nueva respuesta. Dentro del elemento <sqlCacheDependency>
, agregue las bases de datos y la referencia a las cadenas de conexión dentro del elemento <databases>
, como se muestra en el siguiente ejemplo.
<system.web>
<caching>
<sqlCacheDependency enabled="true" pollTime="1000">
<databases>
<add name="MyTestDatabase" connectionStringName="connectString" />
</databases>
</sqlCacheDependency>
<!-- ... -->
</caching>
<!-- ... -->
</system.web>
Luego, configure la caché de salida dentro del elemento <caching>
, como se muestra en el siguiente ejemplo.
<system.web>
<caching>
<!-- ... -->
<outputCacheSettings>
<outputCacheProfiles>
<add name="CacheFor60Seconds" duration="60" varyByParam="none" sqlDependency="MyTestDatabase:MyTable" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
<!-- ... -->
</system.web>
Aquí, la duración de la caché está establecida en 60 segundos, varyByParam
no está establecido en ningún valor y sqlDependency
está establecido en una lista delimitada por punto y coma de pares de nombre/tabla de base de datos separados por dos puntos. Cuando los datos de MyTable
se cambian, se elimina la respuesta almacenada en memoria caché para la operación del servicio y, cuando se invoca la operación, la nueva respuesta se genera (llamando a la operación del servicio), se almacena en memoria caché y se devuelve al cliente.
Importante
Para que ASP.NET pueda acceder a una base de datos SQL, debe usar la herramienta ASP.NET SQL Server Registration Tool. Además, se debe permitir el acceso de la cuenta de usuario correspondiente tanto a la base de datos como a la tabla. Para más información, consulte Acceso a SQL Server desde una aplicación web.
Almacenamiento en memoria caché basado en HTTP GET condicional
En casos en que se usa Web HTTP, los servicios suelen usar un protocolo HTTP GET condicional para implementar el almacenamiento en caché HTTP inteligente descrito en la especificación de HTTP. Para ello, el servicio debe establecer el valor del encabezado ETag en la respuesta HTTP. También debe comprobar el encabezado If-None-Match en la solicitud HTTP para ver si alguno de los ETag especificados coincide con el ETag actual.
Para las solicitudes GET y HEAD, CheckConditionalRetrieve toma un valor de ETag y lo comprueba con respecto al encabezado If-None-Match de la solicitud. Si el encabezado está presente y hay una coincidencia, se produce WebFaultException con un código de estado HTTP 304 (No modificado) y se agrega un encabezado ETag a la respuesta con la ETag coincidente.
Una sobrecarga del método CheckConditionalRetrieve toma la última fecha modificada y la compara con el encabezado If-Modified-Since de la solicitud. Si el encabezado está presente y el recurso no se ha modificado desde entonces, se produce WebFaultException con un código de estado HTTP 304 (No modificado).
Para las solicitudes PUT, POST y DELETE, CheckConditionalUpdate toma el valor de ETag actual de un recurso. Si el valor de ETag actual es nulo, el método comprueba que el encabezado If-None- Match tenga un valor de "*". Si el valor de ETag actual no es un valor predeterminado, el método comprueba el valor de ETag actual con respecto al encabezado If-Match de la solicitud. En cualquier caso, el método produce WebFaultException con un código de estado HTTP 412 (Error de condición previa) si el encabezado esperado no se encuentra en la solicitud o si su valor no satisface la comprobación condicional y establece el encabezado de ETag de la respuesta al valor de ETag actual.
Tanto los métodos CheckConditional
como el método SetETag garantizan que el valor de ETag definido en el encabezado de respuesta es un ETag válido según la especificación de HTTP. Esto incluye la delimitación del valor de ETag con comillas dobles si aún no están presentes y el escape correspondiente de cualquier carácter interno de comillas dobles. No se admite la comparación de ETag débil.
En el siguiente ejemplo, se muestra cómo utilizar estos 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;
}
}
Consideraciones sobre la seguridad
Las solicitudes que requieren autorización no deberían tener sus respuestas almacenadas en memoria caché, porque la autorización no se realiza cuando la respuesta se sirve desde la memoria caché. Si se almacena en memoria caché tales respuestas, se introduce una vulnerabilidad de seguridad grave. Normalmente, las solicitudes que requieren autorización proporcionan los datos específicos del usuario y, por consiguiente, el almacenamiento en caché del lado servidor no es beneficioso. En tales situaciones, será más apropiado usar el almacenamiento en caché del lado cliente o, simplemente, no usar el almacenamiento en memoria caché.