web-dev-qa-db-fra.com

MVC4 StyleBundle: pouvez-vous ajouter une chaîne de requête contournant le cache en mode débogage?

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"/>
47
growse

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.

45
bingles

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.

41
H Dog

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

Vous pouvez appliquer HashCache à tous les bundles d'une BundlesCollection

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();

Ou vous pouvez appliquer HashCache à un seul bundle

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());
13
Rafe

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")
8
Johan Gov

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

2
Hao Kung

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:

  • Fonctionne pour la v1.0.0.0 de System.Web.Optimisations
  • Fonctionne sur plusieurs fichiers dans le bundle
  • Obtient la date de modification du fichier, plutôt que le hachage, de chaque fichier, plutôt qu'un groupe

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)

1
Todd