J'ai une application MVC et j'utilise la classe StyleBundle
pour rendre des fichiers CSS comme ceci:
bundles.Add(new StyleBundle("~/bundles/css").Include("~/Content/*.css"));
Le problème que j'ai est qu'en mode Debug
, les URL CSS sont rendues individuellement, et j'ai un proxy Web qui met agressivement en cache ces URL. En mode Release
, je sais qu'une chaîne de requête est ajoutée à l'url finale pour invalider les caches pour chaque version.
Est-il possible de configurer StyleBundle
pour ajouter une chaîne de requête aléatoire en mode Debug
afin de produire la sortie suivante pour contourner le problème de mise en cache?
<link href="/stylesheet.css?random=some_random_string" rel="stylesheet"/>
Pour ce faire, vous pouvez créer une classe IBundleTransform personnalisée. Voici un exemple qui ajoutera un paramètre v = [filehash] en utilisant un hachage du contenu du fichier.
public class FileHashVersionBundleTransform: IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
foreach(var file in response.Files)
{
using(FileStream fs = File.OpenRead(HostingEnvironment.MapPath(file.IncludedVirtualPath)))
{
//get hash of file contents
byte[] fileHash = new SHA256Managed().ComputeHash(fs);
//encode file hash as a query string param
string version = HttpServerUtility.UrlTokenEncode(fileHash);
file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", version);
}
}
}
}
Vous pouvez ensuite enregistrer la classe en l'ajoutant à la collection Transforms de vos bundles.
new StyleBundle("...").Transforms.Add(new FileHashVersionBundleTransform());
Maintenant, le numéro de version ne changera que si le contenu du fichier change.
Vous avez juste besoin d'une chaîne unique. Ce ne doit pas être Hash. Nous utilisons la date LastModified du fichier et obtenons les ticks à partir de là. L'ouverture et la lecture du fichier coûtent cher, comme l'a noté @Todd. Les tiques suffisent pour sortir un numéro unique qui change lorsque le fichier est modifié.
internal static class BundleExtensions
{
public static Bundle WithLastModifiedToken(this Bundle sb)
{
sb.Transforms.Add(new LastModifiedBundleTransform());
return sb;
}
public class LastModifiedBundleTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
foreach (var file in response.Files)
{
var lastWrite = File.GetLastWriteTime(HostingEnvironment.MapPath(file.IncludedVirtualPath)).Ticks.ToString();
file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", lastWrite);
}
}
}
}
et comment l'utiliser:
bundles.Add(new StyleBundle("~/bundles/css")
.Include("~/Content/*.css")
.WithLastModifiedToken());
et c'est ce que MVC écrit:
<link href="bundles/css/site.css?v=635983900813469054" rel="stylesheet"/>
fonctionne très bien avec les bundles de script aussi.
Cette bibliothèque peut ajouter le hachage de contournement du cache à vos fichiers de bundle en mode débogage, ainsi que quelques autres fonctions de contournement du cache: https://github.com/kemmis/System.Web.Optimization.HashCache
Exécutez la méthode d'extension ApplyHashCache () sur l'instance BundlesCollection une fois que tous les bundles ont été ajoutés à la collection .
BundleTable.Bundles.ApplyHashCache();
Créez une instance de HashCacheTransform et ajoutez-la à l'instance de bundle à laquelle vous souhaitez appliquer HashCache.
var myBundle = new ScriptBundle("~/bundle_virtual_path").Include("~/scripts/jsfile.js");
myBundle.Transforms.Add(new HashCacheTransform());
J'ai eu le même problème mais avec des versions mises en cache dans les navigateurs clients après une mise à niveau. Ma solution consiste à encapsuler l'appel à @Styles.Render("~/Content/css")
dans mon propre moteur de rendu qui ajoute notre numéro de version dans la chaîne de requête comme ceci:
public static IHtmlString RenderCacheSafe(string path)
{
var html = Styles.Render(path);
var version = VersionHelper.GetVersion();
var stringContent = html.ToString();
// The version should be inserted just before the closing quotation mark of the href attribute.
var versionedHtml = stringContent.Replace("\" rel=", string.Format("?v={0}\" rel=", version));
return new HtmlString(versionedHtml);
}
Et puis dans la vue j'aime ça:
@RenderHelpers.RenderCacheSafe("~/Content/css")
Pas actuellement, mais cela devrait être ajouté bientôt (actuellement prévu pour la version 1.1 stable, vous pouvez suivre ce problème ici: Codeplex
Notez que cela est écrit pour les scripts mais fonctionne également pour les styles (changez simplement ces mots clés)
S'appuyant sur la réponse de @ Johan:
public static IHtmlString RenderBundle(this HtmlHelper htmlHelper, string path)
{
var context = new BundleContext(htmlHelper.ViewContext.HttpContext, BundleTable.Bundles, string.Empty);
var bundle = System.Web.Optimization.BundleTable.Bundles.GetBundleFor(path);
var html = System.Web.Optimization.Scripts.Render(path).ToString();
foreach (var item in bundle.EnumerateFiles(context))
{
if (!html.Contains(item.Name))
continue;
html = html.Replace(item.Name, item.Name + "?" + item.LastWriteTimeUtc.ToString("yyyyMMddHHmmss"));
}
return new HtmlString(html);
}
public static IHtmlString RenderStylesBundle(this HtmlHelper htmlHelper, string path)
{
var context = new BundleContext(htmlHelper.ViewContext.HttpContext, BundleTable.Bundles, string.Empty);
var bundle = System.Web.Optimization.BundleTable.Bundles.GetBundleFor(path);
var html = System.Web.Optimization.Styles.Render(path).ToString();
foreach (var item in bundle.EnumerateFiles(context))
{
if (!html.Contains(item.Name))
continue;
html = html.Replace(item.Name, item.Name + "?" + item.LastWriteTimeUtc.ToString("yyyyMMddHHmmss"));
}
return new HtmlString(html);
}
Usage:
@Html.RenderBundle("...")
@Html.RenderStylesBundle("...")
Remplacement
@Scripts.Render("...")
@Styles.Render("...")
Avantages:
En outre, lorsque vous devez contourner rapidement Bundler:
public static MvcHtmlString ResolveUrl(this HtmlHelper htmlHelper, string url)
{
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
var resolvedUrl = urlHelper.Content(url);
if (resolvedUrl.ToLower().EndsWith(".js") || resolvedUrl.ToLower().EndsWith(".css"))
{
var localPath = HostingEnvironment.MapPath(resolvedUrl);
var fileInfo = new FileInfo(localPath);
resolvedUrl += "?" + fileInfo.LastWriteTimeUtc.ToString("yyyyMMddHHmmss");
}
return MvcHtmlString.Create(resolvedUrl);
}
Usage:
<script type="text/javascript" src="@Html.ResolveUrl("~/Scripts/jquery-1.9.1.min.js")"></script>
Remplacement:
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")"></script>
(Remplace également de nombreuses autres recherches alternatives)