vista patron mvc modelo mediante form ejemplos desarrollo controlador aplicaciones c# asp.net-mvc caching architecture asp.net-mvc-3

c# - patron - ¿Cómo implementar un modelo de caché sin violar el patrón MVC?



mvc php (5)

He aceptado la respuesta de @ Josh, pero pensé que agregaría mi propia respuesta, porque no encajé exactamente con lo que sugirió (cerrar), así que pensé en completar lo que realmente hice.

La clave es que ahora estoy usando System.Runtime.Caching . Como esto existe en un ensamblado que es .NET específico y no específico de ASP.NET, no tengo problemas para hacer referencia a esto en mi servicio.

Entonces, todo lo que he hecho es poner la lógica de almacenamiento en caché en los métodos de capa de servicio específicos que necesitan el almacenamiento en caché.

Y un punto importante, estoy trabajando en la clase System.Runtime.Caching.ObjectCache : esto es lo que get inyectará en el constructor del servicio.

Mi DI actual inyecta un objeto System.Runtime.Caching.MemoryCache . Lo bueno de la clase ObjectCache es que es abstracta y todos los métodos principales son virtuales.

Lo que significa que para mis pruebas unitarias, he creado una clase MockCache , anulando todos los métodos e implementando el mecanismo de caché subyacente con un Dictionary<TKey,TValue> simple Dictionary<TKey,TValue> .

Planeamos cambiar pronto a Velocity , así que de nuevo, todo lo que ObjectCache que hacer es crear otra clase de derivación de ObjectCache y estoy listo para continuar.

Gracias por ayudar a todos!

Tengo una aplicación web ASP.NET MVC 3 (Razor), con una página particular que requiere mucha base de datos , y la experiencia del usuario es de máxima prioridad.

Por lo tanto, estoy introduciendo el almacenamiento en caché en esta página en particular.

Estoy tratando de encontrar una manera de implementar este patrón de caché mientras mantengo mi controlador delgado , como lo es actualmente sin almacenamiento en caché:

public PartialViewResult GetLocationStuff(SearchPreferences searchPreferences) { var results = _locationService.FindStuffByCriteria(searchPreferences); return PartialView("SearchResults", results); }

Como puede ver, el controlador es muy delgado, como debería ser. No le importa cómo o dónde está obteniendo su información, ese es el trabajo del servicio.

Un par de notas sobre el flujo de control:

  1. Los controladores obtienen un servicio particular DI, dependiendo de su área. En este ejemplo, este controlador obtiene un LocationService
  2. Los servicios llaman a un repositorio IQueryable<T> y materializan los resultados en T o ICollection<T> .

Cómo quiero implementar el almacenamiento en caché:

  • No puedo usar el almacenamiento en caché de resultados , por algunas razones. En primer lugar, este método de acción se invoca desde el lado del cliente (jQuery / AJAX), a través de [HttpPost] , que según los estándares HTTP no debe almacenarse en caché como una solicitud. En segundo lugar, no quiero almacenar en caché solo en base a los argumentos de solicitud HTTP; la lógica de caché es mucho más complicada que eso; en realidad, hay un caché de dos niveles en marcha.
  • Como sugiero más arriba, necesito usar el almacenamiento en caché de datos, por ejemplo, Cache["somekey"] = someObj; .
  • No quiero implementar un mecanismo genérico de caché en el que todas las llamadas a través del servicio pasen primero por la memoria caché; solo quiero almacenar en caché este método de acción en particular .

Las primeras consideraciones me dicen que cree otro servicio (que hereda LocationService ) y que proporcione el flujo de trabajo de caché (primero, compruebe el caché, si no existe, llame a db, agréguelo al caché, devuelva el resultado).

Eso tiene dos problemas:

  1. Los servicios son Bibliotecas de clases básicas, sin referencias a nada extra. Necesitaría agregar una referencia a System.Web aquí.
  2. Tendría que acceder al contexto HTTP fuera de la aplicación web, lo que se considera una mala práctica, no solo para la capacidad de prueba, sino en general, ¿no?

También pensé en usar la carpeta Models en la aplicación web (que actualmente uso solo para ViewModels ), pero tener un servicio de caché en una carpeta de modelos simplemente no suena bien.

Entonces, ¿alguna idea? ¿Hay alguna cosa específica de MVC (como Action Filter, por ejemplo) que pueda usar aquí?

Consejos / consejos generales serían muy apreciados.


Mi respuesta se basa en la suposición de que sus servicios implementan una interfaz, por ejemplo, el tipo de _locationService es en realidad ILocationService pero se inyecta con un LocationService concreto. Cree un CachingLocationService que implemente la interfaz ILocationService y cambie la configuración de su contenedor para inyectar esa versión de almacenamiento en caché del servicio a este controlador. El CachingLocationService tendría una dependencia en ILocationService que se inyectaría con la clase LocationService original. Utilizaría esto para ejecutar la lógica de negocio real y preocuparse solo por tirar y empujar desde el caché.

No es necesario crear CachingLocationService en el mismo ensamblaje que el LocationService original. Podría estar en su conjunto web. Sin embargo, personalmente lo pondría en el ensamblaje original y agregaría la nueva referencia.

En cuanto a agregar una dependencia en HttpContext; puede eliminar esto tomando una dependencia de

Func<HttpContextBase>

e inyectando esto en tiempo de ejecución con algo como

() => HttpContext.Current

Luego, en sus pruebas puede simular HttpContextBase, pero puede tener problemas para burlarse del objeto Cache sin usar algo como TypeMock.

Editar: Al seguir leyendo en el espacio de nombres .NET 4 System.Runtime.Caching, su CachingLocationService debe tomar una dependencia en ObjectCache. Esta es la clase base abstracta para las implementaciones de caché. A continuación, puede inyectar eso con System.Runtime.Caching.MemoryCache.Default, por ejemplo.


Parece que estás intentando almacenar en caché los datos que obtienes de tu base de datos . Así es como manejo esto (un enfoque que he visto utilizado en muchos proyectos MVC de código abierto):

/// <summary> /// remove a cached object from the HttpRuntime.Cache /// </summary> public static void RemoveCachedObject(string key) { HttpRuntime.Cache.Remove(key); } /// <summary> /// retrieve an object from the HttpRuntime.Cache /// </summary> public static object GetCachedObject(string key) { return HttpRuntime.Cache[key]; } /// <summary> /// add an object to the HttpRuntime.Cache with an absolute expiration time /// </summary> public static void SetCachedObject(string key, object o, int durationSecs) { HttpRuntime.Cache.Add( key, o, null, DateTime.Now.AddSeconds(durationSecs), Cache.NoSlidingExpiration, CacheItemPriority.High, null); } /// <summary> /// add an object to the HttpRuntime.Cache with a sliding expiration time. sliding means the expiration timer is reset each time the object is accessed, so it expires 20 minutes, for example, after it is last accessed. /// </summary> public static void SetCachedObjectSliding(string key, object o, int slidingSecs) { HttpRuntime.Cache.Add( key, o, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 0, slidingSecs), CacheItemPriority.High, null); } /// <summary> /// add a non-removable, non-expiring object to the HttpRuntime.Cache /// </summary> public static void SetCachedObjectPermanent(string key, object o) { HttpRuntime.Cache.Remove(key); HttpRuntime.Cache.Add( key, o, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); }

Tengo esos métodos en una clase estática llamada Current.cs . A continuación, le mostramos cómo puede aplicar esos métodos a su acción de controlador:

public PartialViewResult GetLocationStuff(SearchPreferences searchPreferences) { var prefs = (object)searchPreferences; var cachedObject = Current.GetCachedObject(prefs); // check cache if(cachedObject != null) return PartialView("SearchResults", cachedObject); var results = _locationService.FindStuffByCriteria(searchPreferences); Current.SetCachedObject(prefs, results, 60); // add to cache for 60 seconds return PartialView("SearchResults", results); }


Proporcionaré consejos generales y espero que te dirijan en la dirección correcta.

  1. Si esta es su primera puñalada en el almacenamiento en caché de su aplicación, entonces no almacene en caché la respuesta HTTP, sino que guarde en caché los datos de la aplicación. Por lo general, comienzas con datos de almacenamiento en caché y dando a tu base de datos algo de espacio para respirar; luego, si no es suficiente y sus servidores de aplicaciones / web están bajo un gran estrés, puede pensar en almacenar en caché las respuestas HTTP.

  2. Trate su capa de caché de datos como otro Modelo en el paradigma MVC con todas las implicaciones posteriores.

  3. Hagas lo que hagas, no escribas tu propio caché. Siempre parece más fácil de lo que realmente es. Usa algo como memcached.


Un atributo de acción parece una buena manera de lograr esto. Aquí hay un ejemplo (descargo de responsabilidad: estoy escribiendo esto desde lo más alto de mi cabeza: he consumido una cierta cantidad de cerveza al escribir esto, así que asegúrese de probarlo extensamente :-)):

public class CacheModelAttribute : ActionFilterAttribute { private readonly string[] _paramNames; public CacheModelAttribute(params string[] paramNames) { // The request parameter names that will be used // to constitute the cache key. _paramNames = paramNames; } public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); var cache = filterContext.HttpContext.Cache; var model = cache[GetCacheKey(filterContext.HttpContext)]; if (model != null) { // If the cache contains a model, fetch this model // from the cache and short-circuit the execution of the action // to avoid hitting the repository var result = new ViewResult { ViewData = new ViewDataDictionary(model) }; filterContext.Result = result; } } public override void OnResultExecuted(ResultExecutedContext filterContext) { base.OnResultExecuted(filterContext); var result = filterContext.Result as ViewResultBase; var cacheKey = GetCacheKey(filterContext.HttpContext); var cache = filterContext.HttpContext.Cache; if (result != null && result.Model != null && cache[key] == null) { // If the action returned some model, // store this model into the cache cache[key] = result.Model; } } private string GetCacheKey(HttpContextBase context) { // Use the request values of the parameter names passed // in the attribute to calculate the cache key. // This function could be adapted based on the requirements. return string.Join( "_", (_paramNames ?? Enumerable.Empty<string>()) .Select(pn => (context.Request[pn] ?? string.Empty).ToString()) .ToArray() ); } }

Y entonces su acción de controlador podría verse así:

[CacheModel("id", "name")] public PartialViewResult GetLocationStuff(SearchPreferences searchPreferences) { var results = _locationService.FindStuffByCriteria(searchPreferences); return View(results); }

Y en lo que se refiere a su problema al hacer referencia al ensamblado System.Web en la capa de servicio, ya no es un problema en .NET 4.0. Hay un ensamblaje completamente nuevo que proporciona funciones de almacenamiento en caché extensible: System.Runtime.Caching , por lo que podría usar esto para implementar el almacenamiento en caché directamente en la capa de servicio.

O incluso mejor si está utilizando un ORM en su capa de servicio, ¿probablemente este ORM proporciona capacidades de almacenamiento en caché? Espero que sí. Por ejemplo, NHibernate proporciona un caché de segundo nivel .