web-dev-qa-db-fra.com

En-tête modifié en dernier dans MVC

J'ai récemment rencontré l'en-tête Last-Modified.

  • Comment et où puis-je l'inclure dans MVC?
  • Quels sont les avantages de l'inclure?

Je veux un exemple de la façon dont le dernier en-tête modifié peut être inclus dans un projet mvc, pour les pages statiques et les requêtes de base de données?

Est-ce différent de outputcache, si oui comment?

Fondamentalement, je souhaite que le navigateur vide le cache et affiche automatiquement les dernières données ou pages, sans que l'utilisateur ait besoin d'actualiser ou de vider le cache.

40
learning

Le Last-Modified est principalement utilisé pour la mise en cache. Il est renvoyé pour les ressources dont vous pouvez suivre l'heure de modification. Les ressources ne doivent pas être des fichiers mais n'importe quoi. par exemple des pages qui sont générées à partir d'informations dB où vous avez une colonne UpdatedAt.

Il est utilisé en combinaison avec le If-Modified-Since en-tête que chaque navigateur envoie dans la demande (s'il a reçu un Last-Modified en-tête précédemment).

Comment et où puis-je l'inclure dans MVC?

Response.AddHeader

Quels sont les avantages de l'inclure?

Activez la mise en cache fine pour les pages générées dynamiquement (par exemple, vous pouvez utiliser votre champ de base de données UpdatedAt comme dernier en-tête modifié).

Exemple

Pour que tout fonctionne, vous devez faire quelque chose comme ceci:

public class YourController : Controller
{
    public ActionResult MyPage(string id)
    {
        var entity = _db.Get(id);
        var headerValue = Request.Headers['If-Modified-Since'];
        if (headerValue != null)
        {
            var modifiedSince = DateTime.Parse(headerValue).ToLocalTime();
            if (modifiedSince >= entity.UpdatedAt)
            {
                return new HttpStatusCodeResult(304, "Page has not been modified");
            }
        }

        // page has been changed.
        // generate a view ...

        // .. and set last modified in the date format specified in the HTTP rfc.
        Response.AddHeader('Last-Modified', entity.UpdatedAt.ToUniversalTime().ToString("R"));
    }
}

Vous devrez peut-être spécifier un format dans DateTime.Parse.

Références:

Disclamer : Je ne sais pas si ASP.NET/MVC3 prend en charge que vous gérez Last-Modified par toi-même.

Mise à jour

Vous pouvez créer une méthode d'extension:

public static class CacheExtensions
{
    public static bool IsModified(this Controller controller, DateTime updatedAt)
    {
        var headerValue = controller.Request.Headers['If-Modified-Since'];
        if (headerValue != null)
        {
            var modifiedSince = DateTime.Parse(headerValue).ToLocalTime();
            if (modifiedSince >= updatedAt)
            {
                return false;
            }
        }

        return true;
    }

    public static ActionResult NotModified(this Controller controller)
    {
        return new HttpStatusCodeResult(304, "Page has not been modified");
    }   
}

Et puis utilisez-les comme ceci:

public class YourController : Controller
{
    public ActionResult MyPage(string id)
    {
        var entity = _db.Get(id);
        if (!this.IsModified(entity.UpdatedAt))
            return this.NotModified();

        // page has been changed.
        // generate a view ...

        // .. and set last modified in the date format specified in the HTTP rfc.
        Response.AddHeader('Last-Modified', entity.UpdatedAt.ToUniversalTime().ToString("R"));
    }
}
47
jgauffin


MISE À JOUR: Vérifiez ma nouvelle réponse


Comment et où puis-je l'inclure dans MVC?

Le filtre OutputCache intégré fait le travail pour vous et utilise ces en-têtes pour la mise en cache. Le filtre OuputCache utilise le Last-Modified en-tête lorsque vous définissez Location comme Client ou ServerAndClient.

[OutputCache(Duration = 60, Location = "Client")]
public ViewResult PleaseCacheMe()
{
    return View();
}

Quels sont les avantages de l'inclure?

Tirer parti de la mise en cache côté client avec vidage de cache conditionnel

Je veux un exemple de la façon dont le dernier en-tête modifié peut être inclus dans un projet mvc, pour les pages statiques et les requêtes de base de données?

Ce lien contient suffisamment d'informations pour essayer un échantillon. Pour les pages statiques comme le HTML, les images IIS se chargera de définir/vérifier le Last-Modified en-tête et il utilise la dernière date de modification du fichier. Pour les requêtes de base de données, vous pouvez choisir de définir le SqlDependency dans le OutputCache.

Est-ce différent pour outputcache, si oui comment? Quand dois-je inclure l'en-tête Last-Modified et quand utiliser outputcache?

OutputCache est un filtre d'action utilisé pour implémenter le mécanisme de mise en cache dans ASP.NET MVC. Il existe différentes manières d'effectuer la mise en cache à l'aide de OutputCache: mise en cache côté client, mise en cache côté serveur. Last-Modified header est une façon de réaliser la mise en cache côté client. OutputCache le filtre l'utilise lorsque vous définissez Location comme Client.

Si vous optez pour la mise en cache côté client (Last-Modified ou ETag) le cache du navigateur sera automatiquement mis à jour dans la demande suivante et vous n'avez pas besoin de faire F5.

18
VJAI

Dernière modification vs OutputCache

L'attribut OutputCache contrôle la mise en cache des sorties sur votre IIS WebServer. Il s'agit d'une fonctionnalité de serveur spécifique au fournisseur (voir Configurer IIS 7 Caching de sortie ). Je suggère également de lire Exploration du cache dans ASP.NET MVC si vous êtes intéressé par les puissantes capacités de cette technologie .

En-tête de réponse Last-Modified et son équivalent Demande If-Modified-Since les en-têtes sont des représentants du concept de cache de validation (section contrôle du cache ). Ces en-têtes font partie du protocole HTTP et sont spécifiés dans rfc4229

OutputCache et la validation ne sont pas exclusifs, vous pouvez les combiner.

Quel scénario de mise en cache me rend heureux?

Comme d'habitude: cela dépend.

Configurer un OutputCache de 5 secondes sur une page de 100 hits/seconde réduirait considérablement la charge. En utilisant OutputCache, 499 accès sur 500 peuvent être servis à partir du cache (et ne coûtent pas l'aller-retour, les calculs et le rendu en db).

Lorsque je dois servir rarement des changements immédiatement, le scénario de validation pourrait économiser beaucoup de bande passante. Surtout lorsque vous diffusez un contenu volumineux par rapport à un message d'état Lean 304. Cependant, les changements sont adoptés immédiatement puisque chaque demande valide les changements dans la source.

Exemple d'implémentation d'attribut modifié en dernier

Sur la base de mon expérience, je recommanderais d'implémenter le scénario de validation (dernière modification) en tant qu'attribut de filtre d'action. (Btw: ici est un autre scénario de mise en cache implémenté comme attribut)

Contenu statique du fichier

[LastModifiedCache]
public ActionResult Static()
{
    return File("c:\data\static.html", "text/html");
}

Échantillon de contenu dynamique

[LastModifiedCache]
public ActionResult Dynamic(int dynamicId)
{
    // get data from your backend (db, cache ...)
    var model = new DynamicModel{
        Id = dynamivId,
        LastModifiedDate = DateTime.Today
    };
    return View(model);
}

public interface ILastModifiedDate
{
    DateTime LastModifiedDate { get; }
}

public class DynamicModel : ILastModifiedDate
{
    public DateTime LastModifiedDate { get; set; }
}

L'attribut LastModifiedCache

public class LastModifiedCacheAttribute : ActionFilterAttribute 
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is FilePathResult)
        {
            // static content is served from file in my example
            // the last file write time is taken as modification date
            var result = (FilePathResult) filterContext.Result;
            DateTime lastModify = new FileInfo(result.FileName).LastWriteTime;

            if (!HasModification(filterContext.RequestContext, lastModify))
                filterContext.Result = NotModified(filterContext.RequestContext, lastModify);
            SetLastModifiedDate(filterContext.RequestContext, lastModify);
        }

        if (filterContext.Controller.ViewData.Model is HomeController.ILastModifiedDate)
        {
            // dynamic content assumes the ILastModifiedDate interface to be implemented in the model
            var modifyInterface = (HomeController.ILastModifiedDate)filterContext.Controller.ViewData.Model;
            DateTime lastModify = modifyInterface.LastModifiedDate;

            if (!HasModification(filterContext.RequestContext, lastModify))
                filterContext.Result = NotModified(filterContext.RequestContext, lastModify);
            filterContext.RequestContext.HttpContext.Response.Cache.SetLastModified(lastModify);
        }

        base.OnActionExecuted(filterContext);
    }

    private static void SetLastModifiedDate(RequestContext requestContext, DateTime modificationDate)
    {
        requestContext.HttpContext.Response.Cache.SetLastModified(modificationDate);
    }

    private static bool HasModification(RequestContext context, DateTime modificationDate)
    {
        var headerValue = context.HttpContext.Request.Headers["If-Modified-Since"];
        if (headerValue == null)
            return true;

        var modifiedSince = DateTime.Parse(headerValue).ToLocalTime();
        return modifiedSince < modificationDate;
    }

    private static ActionResult NotModified(RequestContext response, DateTime lastModificationDate)
    {
        response.HttpContext.Response.Cache.SetLastModified(lastModificationDate);
        return new HttpStatusCodeResult(304, "Page has not been modified");
    }
}

Comment activer le support global LastModified

Vous pouvez ajouter l'attribut LastModifiedCache à la section RegisterGlobalFilters de votre global.asax.cs pour activer globalement ce type de mise en cache dans votre projet mvc.

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    ...
    filters.Add(new LastModifiedCacheAttribute());
    ...
}
15
Lukas Winzenried

Notez que outputcache n'est pas votre seule option ici, et en fait, vous ne voudrez peut-être pas gérer la dernière modification de la même manière. Pour clarifier quelques options:

Option 1 - utilisez [OutputCache]

Dans ce cas, le framework mettra en cache le corps de la réponse en fonction de la durée que vous spécifiez. Il le servira avec Last-Modified défini sur l'heure actuelle et max-age défini sur le temps restant jusqu'à l'expiration de la durée du cache d'origine. Si un client envoie une demande avec If-Modified-Since, alors le framework retournera correctement un 304. Une fois la réponse en cache expirée, la date de la dernière modification sera mise à jour chaque fois qu'une nouvelle réponse sera mise en cache.

  • Avantages: la mise en cache se produit au niveau du contrôleur (peut donc fonctionner pour un contenu partiel ou le même contenu mis en cache sur différentes URL de fin). Vous avez un meilleur contrôle sur la mise en cache - par exemple HttpCacheability.ServerAndPrivate permet à votre serveur de mettre en cache le contenu, mais pas les proxys intermédiaires.
  • Inconvénients: vous n'avez aucun contrôle sur la dernière modification. Lorsque votre cache expire, tous les clients devront télécharger à nouveau le contenu même s'il n'a pas réellement changé

Option 2 - spécifiez les paramètres sur Response.Cache

asp.net a une autre couche de mise en cache en dehors de l'attribut outputcacheat sous la forme de System.Web.OutputCacheModule que toutes les demandes traversent. Cela se comporte comme un cache HTTP juste en face de votre application. Donc, si vous définissez des en-têtes de cache raisonnables sans appliquer l'OutputCacheAttribute, votre réponse sera mise en cache ici à la place. Par exemple:

Response.Cache.SetLastModified(lastModifiedDate); Response.Cache.SetCacheability(HttpCacheability.Public); Response.Cache.SetExpires(DateTime.Now + timespan);

Sur la base de ce qui précède, le module de sortie mettra en cache votre contenu et toutes les demandes adressées à la même URL seront traitées à partir du cache. Les demandes avec If-Modified-Since obtiendraient 304s. (Vous pouvez également utiliser ETags). Lorsque votre cache expire, la prochaine demande atteindra normalement votre application, mais si vous savez que le contenu n'a pas changé, vous pouvez renvoyer la même dernière modification ou ETag que vous l'avez fait précédemment. Une fois cette prochaine réponse mise en cache, les clients suivants pourront alors prolonger la durée de vie de leur cache sans re-télécharger le contenu

  • Avantages: Si vous avez un moyen significatif de déterminer la dernière modification ou ETag, vous en avez le contrôle total et vous pouvez réduire le nombre de téléchargements en double.
  • Inconvénients: la mise en cache est uniquement au niveau de la demande/URL. Ne fonctionne que si vous êtes heureux de définir le contrôle du cache: public

Bien que cette option réduit la probabilité de téléchargements de contenu inutiles, elle ne l'élimine pas - la première demande après l'expiration du cache (du serveur) sera traitée normalement et entraînera un 200, même si un 304 aurait été approprié. C'est probablement pour le mieux, car il permet au cache de récupérer une nouvelle copie du corps de la réponse qu'il aurait jetée lors de son expiration précédente, et donc les demandes futures peuvent être servies directement à partir du cache. Je crois que les caches HTTP peuvent en théorie être plus intelligents que cela et utiliser des 304 pour prolonger leur propre durée de vie du cache, mais celui asp.net ne semble pas le supporter.

(Modifié pour remplacer SetMaxAge par SetExpires dans le code ci-dessus - il semble que IIS/asp.net ne respectera pas les en-têtes max-age à moins que vous ne définissiez également SetSlidingExpiration (true) mais ce paramètre semble empêcher la mise en cache que nous voulons)

4
Gareth

Ceci est ma deuxième réponse après avoir fait quelques recherches sur la mise en cache et OutputCache.

Permettez-moi de répondre d'abord à votre deuxième question.

Quels sont les avantages de l'inclure?

Le navigateur met en cache les réponses renvoyées par le serveur. La mise en cache est contrôlée principalement par trois en-têtes: Cache-Control, Last-Modified et Expires (il y en a d'autres comme ETag vient aussi jouer).

Le Last-Modified l'en-tête indique au navigateur quand la ressource a finalement été modifiée. ressource pourrait être soit fichier statique ou vue créée dynamiquement. Chaque fois que le navigateur fait la demande pour cette ressource, il vérifie auprès du serveur "Hé, j'ai déjà une réponse pour cette demande et c'est Last-Modified la date est telle et telle .. voir que l'utilisateur est déjà fatigué ... si vous retournez un 304, je suis heureux d'utiliser la réponse de mon cache sinon envoyez votre nouvelle réponse rapidement ". (Notez que le le navigateur passe le Last-Modified valeur retournée précédemment par le serveur dans un nouvel en-tête appelé If-Modified-Since)

Idéalement, le serveur devrait lire la valeur du If-Modified-Since en-tête et doit vérifier avec la date de modification actuelle et si elles sont identiques, alors il doit retourner 4 (NON MODIFIÉ) ou il doit renvoyer la nouvelle copie de la ressource en passant à nouveau la date de modification actuelle dans les Last-Modified entête.

L'avantage est mise en cache du navigateur. En tirant parti de la mise en cache du navigateur, le serveur peut éviter de créer une réponse en double et peut également renvoyer une nouvelle réponse si la réponse mise en cache dans le navigateur semble ancienne. Le but ultime est gagner du temps.

Comment et où puis-je l'inclure dans MVC?

Dans le cas de ressources statiques comme des images, des fichiers html et autres, vous n'avez pas à vous soucier de définir Comment et car IIS s'occupe de ce travail. IIS utilise la dernière date de modification du fichier comme Last-Modified valeur d'en-tête.

Dans le cas de pages dynamiques comme un contenu html renvoyé via une action MVC, comment déterminer le Last-Modified valeur d'en-tête? Les pages dynamiques sont principalement axées sur les données et il nous appartient de décider si la réponse retournée précédemment est périmée ou non.

Supposons que vous ayez un blog et que vous ayez une page, que vous affichiez les détails d'un article (pas d'autres détails), puis la version de la page est décidée par la dernière date de modification ou la date de création (si l'article n'est pas encore modifié) du article. Vous devez donc faire le même travail auquel répond @ jgauffindans l'action correspondante qui fournit la vue.

Vous avez demandé dans le commentaire Dois-je l'inclure par action dans le contrôleur?

Si vous pouviez faire abstraction de la logique de lecture de la dernière date de modification de la base de données des actions, vous pourriez accomplir le travail via un filtre d'action en évitant de dupliquer le code tout au long des actions. La question est comment allez-vous retirer les détails des actions? Comme passer les noms de table/colonne à l'attribut? Vous devez le comprendre!

Par exemple..

[LastModifiedCacheFilter(Table = "tblArticles", Column = "last_modified")]
public ViewResult Post(int postId)
{
   var post = ... get the post from database using the postId
   return View(post);
}

Le pseudo-code (signifie que je n'ai pas testé cela :) de l'implémentation LastModifiedCacheFilterAttribute montrée ci-dessous utilise Table/Column pour lire la dernière date modifiée, mais cela pourrait également être d'une autre manière. L'idée est dans la méthode OnActionExecuting nous faisons la vérification et retournons un 304 (si le cache est encore frais) et dans la méthode OnResultExecuted nous lisons/fixons la dernière date de modification.

public class LastModifiedCacheFilterAttribute : ActionFilterAttribute
{
    // Could be some other things instead of Table/Column
    public string Table { get; set; }
    public string Column { get; set; }    

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
      // var lastModified = read the value from the passed Column/Table and set it here 

      var ifModifiedSinceHeader = filterContext.RequestContext.HttpContext.Request.Headers["If-Modified-Since"];

      if (!String.IsNullOrEmpty(ifModifiedSinceHeader))
      {
        var modifiedSince = DateTime.Parse(ifModifiedSinceHeader).ToLocalTime();
        if (modifiedSince >= lastModified)
        {
          filterContext.Result = new EmptyResult();
          filterContext.RequestContext.HttpContext.Response.Cache.SetLastModified(lastModified.ToUniversalTime());
          filterContext.RequestContext.HttpContext.Response.StatusCode = 304;
        }
      }

      base.OnActionExecuting(filterContext);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
      // var lastModified = read the value from the passed Column/Table and set it herefilterContext.RequestContext.HttpContext.Response.Cache.SetLastModified(lastModified.ToUniversalTime());
      base.OnResultExecuted(filterContext);
    }
}

Pourquoi l'attribut OutputCache ne peut-il pas?

Selon mon analyse, l'attribut OutputCache n'utilise pas Last-Modified mécanisme de mise en cache. L'autre chose est qu'il utilise l'ancien mécanisme de mise en cache des pages, ce qui rend difficile la personnalisation/l'extension.

Avez-vous vraiment besoin d'implémenter le mécanisme modifié en dernier dans toutes vos actions?

Vraiment pas nécessaire. Vous pouvez implémenter le mécanisme modifié en dernier aux actions qui prennent plus de temps pour créer une telle réponse et cela prend plus de temps pour voyager la réponse sur le fil et atteindre le navigateur. Dans d'autres cas, je pense que c'est juste un frais généraux à mettre en œuvre dans toutes les actions et vous devez également mesurer les avantages avant de le faire. L'autre point principal est que, dans de nombreux cas, la version de la page est pas seulement décidée par une seule colonne de table cela pourrait être par bien d'autres choses et dans ces cas, il pourrait être plus compliqué de l'implémenter!

n point sur ETag

Bien que la question porte sur Last-Modified header Je devrais parler de ETag avant de cliquer sur le bouton Post Your Answer. Par rapport à Last-Modified (qui repose sur datetime) header ETag header (s'appuie sur une valeur de hachage) est plus précis pour déterminer si la réponse mise en cache dans le navigateur est fraîche ou non, mais il pourrait être peu compliqué à implémenter. IIS inclut également l'en-tête ETag ainsi que le Last-Modified en-tête pour les ressources statiques. Avant de mettre en œuvre l'un de ces mécanismes, lancez une recherche sur Google et voyez s'il existe une bibliothèque qui vous aide!

1
VJAI