2011-01-18 11 views
17

Estoy tratando de usar perfiles de caché para almacenar en caché las acciones secundarias en mi aplicación mvc, pero recibo una excepción: la duración debe ser un número positivo.Caché ChildActions utilizando perfiles de caché no funcionará?

Mi web.config se ve así:

<caching> 
     <outputCache enableOutputCache="true" /> 
     <outputCacheSettings> 
     <outputCacheProfiles> 
      <add name="TopCategories" duration="3600" enabled="true" varyByParam="none" /> 
     </outputCacheProfiles> 
     </outputCacheSettings> 
</caching> 

Y mi acción hijo algo como esto:

[ChildActionOnly] 
[OutputCache(CacheProfile = "TopCategories")] 
//[OutputCache(Duration = 60)] 
public PartialViewResult TopCategories() 
{ 
    //... 
    return PartialView(); 
} 

Dentro de una vista que acabo de llamar @Html.RenderAction("TopCategories", "Category")

pero me da un error : Detalles de la excepción: System.InvalidOperationException: la duración debe ser un número positivo.

Si no uso el perfil de caché, funciona. ¿Tienes una idea de cuál es el problema?

Respuesta

17

he hecho un poco de excavación en un related question y mirando a la fuente MVC 3, que definitivamente no son compatibles con cualquier atributo que no sea Duración y VaryByParam. El error principal con su implementación actual es que si no proporciona ninguno de estos, obtendrá una excepción diciéndole que suministre eso, en lugar de una excepción, diga que lo que intentó usar no es compatible. El otro problema importante es que almacenarán en caché incluso si desactivas el almacenamiento en caché en el archivo web.config, lo que parece realmente poco convincente y no correcto.

El mayor problema que tuve con todo esto es que están usando el mismo atributo que funciona tanto en vistas como en vistas parciales, pero en realidad deberían ser 2 atributos diferentes ya que la vista parcial es muy limitada y se comporta mucho de manera diferente, al menos en su implementación actual.

+0

Gracias por su respuesta :) – frennky

+2

He aquí un buen artículo que explica este problema: http://www.dotnetcurry.com/ShowArticle.aspx?ID=665 – frennky

+0

He arreglado esto y presenté una solicitud de extracción de hoy: http://aspnetwebstack.codeplex.com/SourceControl/network/forks/ssmith/OutputCacheAttributeBugfix/contribution/4100 así como escribir un artículo sobre cómo solucionarlo tú mismo (a través de una solicitud de extracción) además de solo comentarlo en SO: http://ardalis.com/how-to-contribute-to-aspnet-yourself – ssmith

2

Aquí está una manera simple si:

  • Su objetivo básico es ser capaz de caché desactivar durante la depuración, y habilitarlo durante el despliegue
  • políticas de almacenamiento en caché Usted no tiene complicados (eso significa que realmente debe respetar la configuración de caché de Web.config)
  • No tiene un sistema de implementación complicado que se basa en la sintaxis de caché de Web.config
  • Ideal si está usando XDT web transformations ya
  • ¡Simplemente asumió que ya funcionaría y está molesto porque no lo hizo y necesita una solución rápida!

Todo lo que hice fue crear un nuevo atributo 'DonutCache'.

[DonutCache] 
public ActionResult HomePageBody(string viewName) 
{ 
    var model = new FG2HomeModel(); 

    return View(viewName, model); 
} 

guardar mi configuración de almacenamiento en caché en Web.config (bajo un nuevo nombre personalizado - con el fin de evitar confusiones).

<appSettings> 
    <add key="DonutCachingDuration" value="5"/> <!-- debug setting --> 
</appSettings> 

Creé un método de ayuda simple para extraer el valor.

public static class Config { 
    public static int DonutCachingDuration 
    { 
     get 
     { 
      return int.Parse(ConfigurationManager.AppSettings["DonutCachingDuration"]); 
     } 
    } 
} 

Por desgracia sólo se puede inicializar un [Attribute] con una constante, por lo que necesita para inicializar el atributo en su constructor (que no puedo acaba de decir [Attribute(Config.DonutCachingDuration)] desafortunadamente).

Nota:: Esto no impide que establezca 'varyByParam' en la declaración [DonutCache], que es actualmente la única propiedad que se puede utilizar para el almacenamiento en caché de los métodos de Acción.

class DonutCacheAttribute : OutputCacheAttribute 
{ 
    public DonutCacheAttribute() 
    { 
     // get cache duration from web.config 
     Duration = Config.DonutCachingDuration; 
    } 
} 

sólo tiene que utilizar una transformación de Web TXD y ya está listo para desplegar con un valor más largo.

<add key="DonutCachingDuration" value="120" 
     xdt:Locator="Match(key)" xdt:Transform="Replace"/> 

Consejo: Es probable que quiere meter un @DateTime.Now.ToString() en su vista parcial para asegurarse de que la configuración de caché está siendo respetado.

-1

En algunos casos, puede ser apropiado simplemente crear un segundo método de acción, con el almacenamiento en caché deshabilitado que es llamado por su acción principal.

/// Use this for normal HTTP requests which need to be cached 
    [OutputCache(CacheProfile = "Script")] 
    public ContentResult Foo(string id) 
    { 
     return _Foo(id); 
    } 

    /// Use this for Html.Action 
    public ContentResult _Foo(string id) 
    { 
     return View(); 
    } 

Cuando necesite Html.Action que acaba de llamar _foo en lugar de Foo.

@Html.Action("_Foo", "Bar").ToString(); 

entonces usted puede confiar en la página principal para hacer el almacenamiento en caché. Si esto no es apropiado (porque no desea almacenar en caché toda la página), puede usar el 'DonutCacheAttribute' de mi otra respuesta.

+0

Esto no permite el uso de [ChildActionOnly], así que no es una respuesta – YEH

17

llegué solucionar el problema mediante la creación de un atributo personalizado OutputCache, que carga manualmente el Duration, VarByCustom y VarByParam del perfil:

public class ChildActionOutputCacheAttribute : OutputCacheAttribute 
{ 
    public ChildActionOutputCacheAttribute(string cacheProfile) 
    { 
     var settings = (OutputCacheSettingsSection)WebConfigurationManager.GetSection("system.web/caching/outputCacheSettings"); 
     var profile = settings.OutputCacheProfiles[cacheProfile]; 
     Duration = profile.Duration; 
     VaryByParam = profile.VaryByParam; 
     VaryByCustom = profile.VaryByCustom; 
    } 
} 

La ventaja de este enfoque es que se llega a seguir manteniendo toda su perfiles en un solo lugar en la web.config.

+0

Cuando publiqué la pregunta, pensé que estaba haciendo algo mal y no sospeché que era un problema de marco. Al final fui con una solución similar. – frennky

+0

Sí, lo mismo me pasó a mí. Y como pude resolver una solución relativamente simple, decidí agregar el id a este hilo viejo en caso de que ayudara a alguien más. –

+3

Funciona muy bien - Tuve que anular 'OnActionExecuting' y llamar a' base.OnActionExecuting' solo cuando el perfil de caché estaba habilitado en web.config. De lo contrario, el temido error de "Duración" volvió a aparecer al configurar 'enabled =" false "'. – marapet

-1

Eso funciona para mí.

public class ChildActionOutputCacheAttribute : OutputCacheAttribute 
{ 
    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     if (filterContext.IsChildAction && !string.IsNullOrWhiteSpace(CacheProfile)) 
     { 
      lock (this.GetType()) 
      { 
       if (!string.IsNullOrWhiteSpace(CacheProfile)) 
       { 
        // OutputCacheAttribute for child actions only supports 
        // Duration, VaryByCustom, and VaryByParam values. 
        var outputCache = (OutputCacheSettingsSection)WebConfigurationManager.GetSection("system.web/caching/outputCacheSettings"); 
        var profile = outputCache.OutputCacheProfiles[CacheProfile]; 
        if (profile.Enabled) 
        { 
         Duration = profile.Duration > 0 ? profile.Duration : Duration; 
         VaryByCustom = string.IsNullOrWhiteSpace(profile.VaryByCustom) 
          ? VaryByCustom : profile.VaryByCustom; 
         VaryByParam = string.IsNullOrWhiteSpace(profile.VaryByParam) 
          ? VaryByParam : profile.VaryByParam; 
        } 
        CacheProfile = null; 
       } 
      } 
     } 
     base.OnActionExecuting(filterContext); 
    } 
}