web-dev-qa-db-fra.com

Comment puis-je mettre en cache des objets dans ASP.NET MVC?

Je voudrais mettre en cache des objets dans ASP.NET MVC. J'ai un BaseController dont je veux que tous les contrôleurs héritent. Dans le BaseController, il y a une propriété User qui va simplement récupérer les données utilisateur de la base de données afin que je puisse les utiliser dans le contrôleur, ou les transmettre aux vues.

Je voudrais mettre ces informations en cache. J'utilise ces informations sur chaque page, il n'est donc pas nécessaire d'accéder à la base de données à chaque demande de page.

J'aimerais quelque chose comme:

if(_user is null)
  GrabFromDatabase
  StuffIntoCache
return CachedObject as User

Comment implémenter la mise en cache simple dans ASP.NET MVC?

50
rball

Vous pouvez toujours utiliser le cache (partagé entre toutes les réponses) et la session (unique par utilisateur) pour le stockage.

J'aime le modèle suivant "essayer d'obtenir à partir du cache/créer et stocker" (pseudocode de type c #):

public static class CacheExtensions
{
  public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator)
  {
    var result = cache[key];
    if(result == null)
    {
      result = generator();
      cache[key] = result;
    }
    return (T)result;
  }
}

vous utiliseriez ceci comme ceci:

var user = HttpRuntime
              .Cache
              .GetOrStore<User>(
                 $"User{_userId}", 
                 () => Repository.GetUser(_userId));

Vous pouvez adapter ce modèle à la session, à ViewState (ugh) ou à tout autre mécanisme de cache. Vous pouvez également étendre ControllerContext.HttpContext (qui, je pense, est l'un des wrappers de System.Web.Extensions), ou créer une nouvelle classe pour le faire avec de la place pour se moquer du cache.

69
Will

J'ai pris la réponse de Will et l'ai modifiée pour rendre la classe CacheExtensions statique et suggérer une légère altération afin de faire face à la possibilité de Func<T> étant null:

public static class CacheExtensions
{

    private static object sync = new object();
    public const int DefaultCacheExpiration = 20;

    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache">calling object</param>
    /// <param name="key">Cache key</param>
    /// <param name="generator">Func that returns the object to store in cache</param>
    /// <returns></returns>
    /// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
    public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator ) {
        return cache.GetOrStore( key, (cache[key] == null && generator != null) ? generator() : default( T ), DefaultCacheExpiration );
    }


    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache">calling object</param>
    /// <param name="key">Cache key</param>
    /// <param name="generator">Func that returns the object to store in cache</param>
    /// <param name="expireInMinutes">Time to expire cache in minutes</param>
    /// <returns></returns>
    public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator, double expireInMinutes ) {
        return cache.GetOrStore( key,  (cache[key] == null && generator != null) ? generator() : default( T ), expireInMinutes );
    }


    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId),_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache">calling object</param>
    /// <param name="key">Cache key</param>
    /// <param name="obj">Object to store in cache</param>
    /// <returns></returns>
    /// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
    public static T GetOrStore<T>( this Cache cache, string key, T obj ) {
        return cache.GetOrStore( key, obj, DefaultCacheExpiration );
    }

    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache">calling object</param>
    /// <param name="key">Cache key</param>
    /// <param name="obj">Object to store in cache</param>
    /// <param name="expireInMinutes">Time to expire cache in minutes</param>
    /// <returns></returns>
    public static T GetOrStore<T>( this Cache cache, string key, T obj, double expireInMinutes ) {
        var result = cache[key];

        if ( result == null ) {

            lock ( sync ) {
                result = cache[key];
                if ( result == null ) {
                    result = obj != null ? obj : default( T );
                    cache.Insert( key, result, null, DateTime.Now.AddMinutes( expireInMinutes ), Cache.NoSlidingExpiration );
                }
            }
        }

        return (T)result;

    }

}

J'envisagerais également d'aller plus loin pour implémenter une solution de session testable qui étend la classe abstraite System.Web.HttpSessionStateBase.

public static class SessionExtension
{
    /// <summary>
    /// 
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpContext
    ///   .Session
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache"></param>
    /// <param name="key"></param>
    /// <param name="generator"></param>
    /// <returns></returns>
    public static T GetOrStore<T>( this HttpSessionStateBase session, string name, Func<T> generator ) {

        var result = session[name];
        if ( result != null )
            return (T)result;

        result = generator != null ? generator() : default( T );
        session.Add( name, result );
        return (T)result;
    }

}
59
njappboy

Si vous voulez qu'il soit mis en cache pour la durée de la demande, mettez-le dans votre classe de base de contrôleur:

public User User {
    get {
        User _user = ControllerContext.HttpContext.Items["user"] as User;

        if (_user == null) {
            _user = _repository.Get<User>(id);
            ControllerContext.HttpContext.Items["user"] = _user;
        }

        return _user;
    }
}

Si vous souhaitez mettre en cache plus longtemps, utilisez le remplacer l'appel ControllerContext par un à cache []. Si vous choisissez d'utiliser l'objet Cache pour mettre en cache plus longtemps, vous devrez utiliser une clé de cache unique car elle sera partagée entre les demandes/utilisateurs.

6
John Sheehan

@njappboy: Belle implémentation. Je ne différerais que l'invocation de Generator( ) jusqu'au dernier moment responsable. vous pouvez donc également mettre en cache les invocations de méthode.

/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
///   .Cache
///   .GetOrStore<User>(
///      string.Format("User{0}", _userId), 
///      () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="Cache">calling object</param>
/// <param name="Key">Cache key</param>
/// <param name="Generator">Func that returns the object to store in cache</param>
/// <returns></returns>
/// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
public static T GetOrStore<T>( this Cache Cache, string Key, Func<T> Generator )
{
    return Cache.GetOrStore( Key, Generator, DefaultCacheExpiration );
}

/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
///   .Cache
///   .GetOrStore<User>(
///      string.Format("User{0}", _userId), 
///      () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="Cache">calling object</param>
/// <param name="Key">Cache key</param>
/// <param name="Generator">Func that returns the object to store in cache</param>
/// <param name="ExpireInMinutes">Time to expire cache in minutes</param>
/// <returns></returns>
public static T GetOrStore<T>( this Cache Cache, string Key, Func<T> Generator, double ExpireInMinutes )
{
    var Result = Cache [ Key ];

    if( Result == null )
    {
        lock( Sync )
        {
            if( Result == null )
            {
                Result = Generator( );
                Cache.Insert( Key, Result, null, DateTime.Now.AddMinutes( ExpireInMinutes ), Cache.NoSlidingExpiration );
            }
        }
    }

    return ( T ) Result;
}
3
SDReyes

J'aime cacher le fait que les données sont mises en cache dans le référentiel. Vous pouvez accéder au cache via la propriété HttpContext.Current.Cache et stocker les informations utilisateur en utilisant "User" + id.ToString () comme clé.

Cela signifie que tous les accès aux données utilisateur à partir du référentiel utiliseront les données mises en cache si elles sont disponibles et ne nécessitent aucune modification de code dans le modèle, le contrôleur ou la vue.

J'ai utilisé cette méthode pour corriger de graves problèmes de performances sur un système qui interrogeait la base de données pour chaque propriété utilisateur et réduisait les temps de chargement des pages de quelques minutes à quelques secondes.

3
Matthew

Quelques autres réponses ici ne traitent pas des éléments suivants:

  • cache-cache
  • serrure à double contrôle

Cela pourrait conduire le générateur (ce qui pourrait prendre un certain temps) à s'exécuter plusieurs fois dans différents threads.

Voici ma version qui ne devrait pas souffrir de ce problème:

// using System;
// using System.Web.Caching;

// https://stackoverflow.com/a/42443437
// Usage: HttpRuntime.Cache.GetOrStore("myKey", () => GetSomethingToCache());

public static class CacheExtensions
{
    private static readonly object sync = new object();
    private static TimeSpan defaultExpire = TimeSpan.FromMinutes(20);

    public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator) =>
        cache.GetOrStore(key, generator, defaultExpire);

    public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator, TimeSpan expire)
    {
        var result = cache[key];
        if (result == null)
        {
            lock (sync)
            {
                result = cache[key];
                if (result == null)
                {
                    result = generator();
                    cache.Insert(key, result, null, DateTime.Now.AddMinutes(expire.TotalMinutes), Cache.NoSlidingExpiration);
                }
            }
        }
        return (T)result;
    }
}
3
DigitalDan

Si vous n'avez pas besoin de fonctionnalités d'invalidation spécifiques de la mise en cache ASP.NET, les champs statiques sont assez bons, légers et faciles à utiliser. Cependant, dès que vous avez besoin des fonctionnalités avancées, vous pouvez basculer vers l'objet Cache d'ASP.NET pour le stockage.

L'approche que j'utilise est de créer une propriété et un champ private. Si le champ est null, la propriété le remplira et le renverra. Je fournis également une méthode InvalidateCache qui définit manuellement le champ sur null. L'avantage de cette approche est que le mécanisme de mise en cache est encapsulé dans la propriété et vous pouvez basculer vers une approche différente si vous le souhaitez.

2
Mehrdad Afshari

Implémentation avec un verrouillage de cache minimal. La valeur stockée dans le cache est enveloppée dans un conteneur. Si la valeur n'est pas dans le cache, le conteneur de valeurs est verrouillé. Le cache n'est verrouillé que lors de la création du conteneur.

public static class CacheExtensions
{
    private static object sync = new object();

    private class Container<T>
    {
        public T Value;
    }

    public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, TimeSpan slidingExpiration)
    {
        return cache.GetOrStore(key, create, Cache.NoAbsoluteExpiration, slidingExpiration);
    }

    public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, DateTime absoluteExpiration)
    {
        return cache.GetOrStore(key, create, absoluteExpiration, Cache.NoSlidingExpiration);
    }

    public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, DateTime absoluteExpiration, TimeSpan slidingExpiration)
    {
        return cache.GetOrCreate(key, x => create());
    }

    public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<string, TValue> create, DateTime absoluteExpiration, TimeSpan slidingExpiration)
    {
        var instance = cache.GetOrStoreContainer<TValue>(key, absoluteExpiration, slidingExpiration);
        if (instance.Value == null)
            lock (instance)
                if (instance.Value == null)
                    instance.Value = create(key);

        return instance.Value;
    }

    private static Container<TValue> GetOrStoreContainer<TValue>(this Cache cache, string key, DateTime absoluteExpiration, TimeSpan slidingExpiration)
    {
        var instance = cache[key];
        if (instance == null)
            lock (cache)
            {
                instance = cache[key];
                if (instance == null)
                {
                    instance = new Container<TValue>();

                    cache.Add(key, instance, null, absoluteExpiration, slidingExpiration, CacheItemPriority.Default, null);
                }
            }

        return (Container<TValue>)instance;
    }
}
1
Ilya Orlov