J'ai récemment rencontré l'en-tête Last-Modified.
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.
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?
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.
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"));
}
}
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.
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.
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());
...
}
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.
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
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)
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 Où 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!