web-dev-qa-db-fra.com

forcer les navigateurs à obtenir les derniers fichiers js et css dans l'application asp.net

Certains navigateurs mettent en cache les fichiers js et css, sans pouvoir les actualiser à moins que vous ne les y obligiez. Quel est le moyen le plus simple?.

Je viens de mettre en œuvre cette solution qui semble fonctionner. 

Déclarer une version variable sur votre page

  public string version { get; set; }

Obtenir le numéro de version de la clé web.config

 version = ConfigurationManager.AppSettings["versionNumber"];

Dans votre page aspx, appelez javascript et les feuilles de style comme

<script src="scripts/myjavascript.js?v=<%=version %>" type="text/javascript"></script>
<link href="styles/mystyle.css?v=<%=version %>" rel="stylesheet" type="text/css" />

Donc, si vous définissez la version = 1.1 à partir de 1.0 dans votre configuration web, votre navigateur téléchargera les derniers fichiers, ce qui vous épargnera une certaine frustration, à vous et à vos utilisateurs.

Y at-il une autre solution qui fonctionne mieux, ou cela causera-t-il des problèmes imprévus pour un site Web?

92
kiev

J'ai résolu ce problème en ajoutant aux scripts un horodatage modifié en tant que paramètre de requête. 

Je l'ai fait avec une méthode d'extension et en l'utilisant dans mes fichiers CSHTML. Remarque: cette implémentation met l’horodatage en mémoire cache pendant 1 minute afin que le disque ne soit pas écrasé autant.

Voici la méthode d'extension:

public static class JavascriptExtension {
    public static MvcHtmlString IncludeVersionedJs(this HtmlHelper helper, string filename) {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<script type='text/javascript' src='" + filename + version + "'></script>");
    }

    private static string GetVersion(this HtmlHelper helper, string filename)
    {
        var context = helper.ViewContext.RequestContext.HttpContext;

        if (context.Cache[filename] == null)
        {
            var physicalPath = context.Server.MapPath(filename);
            var version = $"?v={new System.IO.FileInfo(physicalPath).LastWriteTime.ToString("MMddHHmmss")}";
            context.Cache.Add(filename, version, null,
              DateTime.Now.AddMinutes(5), TimeSpan.Zero,
              CacheItemPriority.Normal, null);
            return version;
        }
        else
        {
            return context.Cache[filename] as string;
        }
    }
}

Et ensuite dans la page CSHTML:

 @Html.IncludeVersionedJs("/MyJavascriptFile.js")

Dans le code HTML rendu, cela apparaît comme:

 <script type='text/javascript' src='/MyJavascriptFile.js?20111129120000'></script>
69
Adam Tegen

Votre solution fonctionne. C'est assez populaire en fait. 

Même Stack Overflow utilise une méthode similaire:

<link rel="stylesheet" href="http://sstatic.net/so/all.css?v=6184"> 

v=6184 est probablement le numéro de révision SVN.

26
Daniel Vassallo

ASP.NET MVC se chargera de cela si vous utilisez des ensembles pour votre JS/CSS. Il ajoutera automatiquement un numéro de version sous la forme d'un GUID à vos ensembles et ne mettra à jour ce GUID que lorsque l'ensemble sera mis à jour (autrement dit, les fichiers source sont modifiés).

Cela aide également si vous avez une tonne de fichiers JS/CSS, car cela peut considérablement améliorer les temps de chargement du contenu!

Vois ici

18
jonesy827

Dans ASP.NET Core (MVC 6), cela fonctionne par l’intermédiaire de l’assistant de balise asp-append-version:

<script src="scripts/myjavascript.js" asp-append-version="true"></script>
<link href="styles/mystyle.css rel="stylesheet" asp-append-version="true" />
17
metalheart

Il existe un moyen intégré dans asp.net pour cela: bundling . Il suffit de l'utiliser. Chaque nouvelle version aura un suffixe unique "? V = XXXXXXX". En mode débogage, la mise en bundle est désactivée.

<system.web>
    <compilation debug="false" />
</system.web>

Ou ajoutez à la méthode RegisterBundles (bundle BundleCollection):

BundleTable.EnableOptimizations = true;

Par exemple:

BundleConfig.cs:

bundles.Add(new ScriptBundle("~/Scripts/myjavascript.js")
                .Include("~/Scripts/myjavascript.js"));

bundles.Add(new StyleBundle("~/Content/mystyle.css")
                .Include("~/Content/mystyle.css"));

_Layout.cshtml:

@Scripts.Render("~/Scripts/myjavascript.js")
@Styles.Render("~/Content/mystyle.css")
10
Alex Tkachuk

Je voulais un simple paquebot pour rendre le chemin unique pour détruire la cache. Cela a fonctionné pour moi:

<script src="scripts/main.js?bust_js_cache=<%=System.IO.File.GetLastWriteTime(Server.MapPath("scripts/main.js")).ToString("HH:mm:ss")%>" type="text/javascript"></script>

Si le fichier a été modifié depuis le dernier chargement sur la page, le navigateur extrait le fichier mis à jour.

Il génère le timbre last modified à partir du fichier .js et le stocke à la place de la version à laquelle il est difficile d'accéder.

<script src="scripts/main.js?bust_js_cache=10:18:38" type="text/javascript"></script>

Une autre option pourrait être d’obtenir la somme de contrôle du fichier.

4
sniperd

Basé sur Réponse d'Adam Tegan , modifiée pour être utilisée dans une application de formulaires Web.

Dans le code de classe .cs:

public static class FileUtility
{
    public static string SetJsVersion(HttpContext context, string filename) {
        string version = GetJsFileVersion(context, filename);
        return filename + version;
    }

    private static string GetJsFileVersion(HttpContext context, string filename)
    {
        if (context.Cache[filename] == null)
        {
            string filePhysicalPath = context.Server.MapPath(filename);

            string version = "?v=" + GetFileLastModifiedDateTime(context, filePhysicalPath, "yyyyMMddhhmmss");

            return version;
        }
        else
        {
            return string.Empty;
        }
    }

    public static string GetFileLastModifiedDateTime(HttpContext context, string filePath, string dateFormat)
    {
        return new System.IO.FileInfo(filePath).LastWriteTime.ToString(dateFormat);
    }
}

Dans le balisage aspx:

<script type="text/javascript" src='<%= FileUtility.SetJsVersion(Context,"/js/exampleJavaScriptFile.js") %>'></script>

Et dans le rendu HTML, il apparaît comme

<script type="text/javascript" src='/js/exampleJavaScriptFile.js?v=20150402021544'></script>
4
Bryan

Il est intéressant de noter que ce site a des problèmes avec l’approche que vous décrivez en relation avec certaines configurations de proxy, même si elle doit être sécurisée. 

Vérifiez ceci Meta Stack Overflow discussion. 

Donc, à la lumière de cela, il pourrait être judicieux de ne pas utiliser un paramètre GET pour mettre à jour, mais le nom de fichier réel:

href="/css/scriptname/versionNumber.css" 

même si cela demande plus de travail, vous devrez créer le fichier ou créer une réécriture d'URL pour celui-ci.

4
Pekka 웃

Voici une approche qui fonctionne avec ASP.NET 5/MVC 6/vNext .

Étape 1: Créez une classe pour renvoyer la dernière heure d'écriture du fichier, similaire aux autres réponses de ce fil de discussion. Notez que cela nécessite l'injection de dépendance ASP.NET 5 (ou autre).

public class FileVersionService
{
    private IHostingEnvironment _hostingEnvironment;
    public FileVersionService(IHostingEnvironment hostingEnvironment)
    {
        _hostingEnvironment = hostingEnvironment;
    }

    public string GetFileVersion(string filename)
    {
       var path = string.Format("{0}{1}", _hostingEnvironment.WebRootPath, filename);
       var fileInfo = new FileInfo(path);
       var version = fileInfo.LastWriteTimeUtc.ToString("yyyyMMddhhmmssfff");
       return version;
     }
}

Étape 2: / Enregistrez le service à injecter dans startup.cs :

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<FileVersionService>();
    ...
}

Étape 3: Ensuite, dans ASP.NET 5, il est possible d'injecter le service directement dans une vue de présentation telle que _Layout.cshtml comme ceci:

@inject Namespace.Here.FileVersionService fileVersionService
<!DOCTYPE html>
<html lang="en" class="@ViewBag.HtmlClass">
<head>
    ...
    <link href="/css/[email protected]("\\css\\styles.css")" rel="stylesheet" />
    ...
</head>
<body>
    ...
</body>

Certaines finitions pourraient être effectuées pour mieux combiner les chemins physiques et gérer le nom du fichier dans un style plus cohérent avec la syntaxe, mais ceci est un point de départ. J'espère que cela aidera les gens à passer à ASP.NET 5.

3
Ender2050

Il y a une réponse plus simple à cela que la réponse donnée par l'op dans la question (l'approche est la même):

Définissez la clé dans le fichier web.config:

<add key="VersionNumber" value="06032014"/>

Appelez directement appsettings depuis la page aspx:

<link href="styles/navigation.css?v=<%=ConfigurationManager.AppSettings("VersionNumber")%>" rel="stylesheet" type="text/css" />
3
JackArbiter

À partir de ci-dessus, réponse J'ai légèrement modifié le code pour que l'assistant fonctionne également avec les fichiers CSS et ajoute une version à chaque fois que vous modifiez les fichiers, et pas uniquement lors de la génération.

public static class HtmlHelperExtensions
{
    public static MvcHtmlString IncludeVersionedJs(this HtmlHelper helper, string filename)
    {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<script type='text/javascript' src='" + filename + version + "'></script>");
    }

    public static MvcHtmlString IncludeVersionedCss(this HtmlHelper helper, string filename)
    {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<link href='" + filename + version + "' type ='text/css' rel='stylesheet'/>");
    }

    private static string GetVersion(this HtmlHelper helper, string filename)
    {
        var context = helper.ViewContext.RequestContext.HttpContext;
        var physicalPath = context.Server.MapPath(filename);
        var version = "?v=" +
        new System.IO.FileInfo(physicalPath).LastWriteTime
        .ToString("yyyyMMddHHmmss");
        context.Cache.Add(physicalPath, version, null,
          DateTime.Now.AddMinutes(1), TimeSpan.Zero,
          CacheItemPriority.Normal, null);

        if (context.Cache[filename] == null)
        {
            context.Cache[filename] = version;
            return version;
        }
        else
        {
            if (version != context.Cache[filename].ToString())
            {
                context.Cache[filename] = version;
                return version;
            }
            return context.Cache[filename] as string;
        }
    }
}
2
Sergi Mulà

J'ai utilisé une technique légèrement différente sur mon site aspnet MVC 4:

_ViewStart.cshtml:

@using System.Web.Caching
@using System.Web.Hosting
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    PageData.Add("scriptFormat", string.Format("<script src=\"{{0}}?_={0}\"></script>", GetDeployTicks()));
}

@functions
{

    private static string GetDeployTicks()
    {
        const string cacheKey = "DeployTicks";
        var returnValue = HttpRuntime.Cache[cacheKey] as string;
        if (null == returnValue)
        {
            var absolute = HostingEnvironment.MapPath("~/Web.config");
            returnValue = File.GetLastWriteTime(absolute).Ticks.ToString();
            HttpRuntime.Cache.Insert(cacheKey, returnValue, new CacheDependency(absolute));
        }
        return returnValue;
    }
}

Puis dans les vues réelles:

 @Scripts.RenderFormat(PageData["scriptFormat"], "~/Scripts/Search/javascriptFile.min.js")
2
Anthony Wolfe

Suggestions préalables simplifiées et fourniture de code pour les développeurs .NET Web Forms.

Cela acceptera les URL relatives ("~ /") et absolues dans le chemin du fichier vers la ressource.

Mettez dans un fichier de classe d'extensions statiques, ce qui suit:

public static string VersionedContent(this HttpContext httpContext, string virtualFilePath)
{
    var physicalFilePath = httpContext.Server.MapPath(virtualFilePath);
    if (httpContext.Cache[physicalFilePath] == null)
    {
        httpContext.Cache[physicalFilePath] = ((Page)httpContext.CurrentHandler).ResolveUrl(virtualFilePath) + (virtualFilePath.Contains("?") ? "&" : "?") + "v=" + File.GetLastWriteTime(physicalFilePath).ToString("yyyyMMddHHmmss");
    }
    return (string)httpContext.Cache[physicalFilePath];
}

Et puis appelez-le dans votre page maître en tant que tel:

<link type="text/css" rel="stylesheet" href="<%= Context.VersionedContent("~/styles/mystyle.css") %>" />
<script type="text/javascript" src="<%= Context.VersionedContent("~/scripts/myjavascript.js") %>"></script>
1
Jason Ellingson

<?php $Rand_no = Rand(10000000, 99999999)?> <script src="scripts/myjavascript.js?v=<?=$Rand_no"></script>

Cela fonctionne pour moi dans tous les navigateurs. Ici, j'ai utilisé PHP pour générer un nombre aléatoire. Vous pouvez utiliser votre propre langue côté serveur. 

1
Mukesh Rai

Obtenir le temps de modification du fichier, comme indiqué ci-dessous

private static string GetLastWriteTimeForFile(string pathVal)
    {
        return System.IO.File.GetLastWriteTime(HostingEnvironment.MapPath(pathVal)).ToFileTime().ToString();
    }

Ajouter ceci avec l'entrée comme chaîne de requête

public static string AppendDateInFile(string pathVal)
    {
        var patheWithDate = new StringBuilder(pathVal);
        patheWithDate.AppendFormat("{0}x={1}",
                               pathVal.IndexOf('?') >= 0 ? '&' : '?',
                               GetLastWriteTimeForFile(pathVal));
        return patheWithDate.ToString();
    }

Appelez cela depuis le balisage.

Approche d'assistance d'extension MVC

Ajouter une méthode d'extension

namespace TNS.Portal.Helpers
{
    public static class ScriptExtensions
    {
        public static HtmlString QueryStringScript<T>(this HtmlHelper<T> html, string path)
        {
            var file = html.ViewContext.HttpContext.Server.MapPath(path);
            DateTime lastModified = File.GetLastWriteTime(file);
            TagBuilder builder = new TagBuilder("script");
            builder.Attributes["src"] = path + "?modified=" + lastModified.ToString("yyyyMMddhhmmss");
            return new HtmlString(builder.ToString());
        }

       public static HtmlString QueryStringStylesheet<T>(this HtmlHelper<T> html, string path)
       {
        var file = html.ViewContext.HttpContext.Server.MapPath(path);
        DateTime lastModified = File.GetLastWriteTime(file);
        TagBuilder builder = new TagBuilder("link");
        builder.Attributes["href"] = path + "?modified=" + lastModified.ToString("yyyyMMddhhmmss");
        builder.Attributes["rel"] = "stylesheet";
        return new HtmlString(builder.ToString());
      }

    }
}

Ajouter cet espace de noms dans web.config

<system.web.webPages.razor>
    <Host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <add namespace="TNS.Portal" />
        <add namespace="TNS.Portal.Helpers" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

Utilisez-le en vue comme

@Html.QueryStringScript("/Scripts/NPIAjaxCalls.js")
@Html.QueryStringStylesheet("/Content/StyledRadio.css")
1
Lijo

Vous pouvez remplacer la propriété DefaultTagFormat de Scripts ou de styles.

Scripts.DefaultTagFormat = @"<script src=""{0}?v=" + ConfigurationManager.AppSettings["pubversion"] + @"""></script>";
Styles.DefaultTagFormat = @"<link href=""{0}?v=" + ConfigurationManager.AppSettings["pubversion"] + @""" rel=""stylesheet""/>";
0
phoenix

Pour résoudre ce problème dans mon application ASP.Net Ajax, j'ai créé une extension, puis appelé dans la page maître. 

Pour plus de détails, vous pouvez passer par le lien link .

0
Kasim Husaini

Basé sur la réponse ci-dessus J'ai écrit une petite classe d'extension pour travailler avec les fichiers CSS et JS:

public static class TimestampedContentExtensions
{
    public static string VersionedContent(this UrlHelper helper, string contentPath)
    {
        var context = helper.RequestContext.HttpContext;

        if (context.Cache[contentPath] == null)
        {
            var physicalPath = context.Server.MapPath(contentPath);
            var version = @"v=" + new FileInfo(physicalPath).LastWriteTime.ToString(@"yyyyMMddHHmmss");

            var translatedContentPath = helper.Content(contentPath);

            var versionedContentPath =
                contentPath.Contains(@"?")
                    ? translatedContentPath + @"&" + version
                    : translatedContentPath + @"?" + version;

            context.Cache.Add(physicalPath, version, null, DateTime.Now.AddMinutes(1), TimeSpan.Zero,
                CacheItemPriority.Normal, null);

            context.Cache[contentPath] = versionedContentPath;
            return versionedContentPath;
        }
        else
        {
            return context.Cache[contentPath] as string;
        }
    }
}

Au lieu d'écrire quelque chose comme:

<link href="@Url.Content(@"~/Content/bootstrap.min.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content(@"~/Scripts/bootstrap.min.js")"></script>

Vous pouvez maintenant écrire:

<link href="@Url.VersionedContent(@"~/Content/bootstrap.min.css")" rel="stylesheet" type="text/css" />
<script src="@Url.VersionedContent(@"~/Scripts/bootstrap.min.js")"></script>

C'est à dire. remplacez simplement Url.Content par Url.VersionedContent.

Les URL générées ressemblent à quelque chose comme:

<link href="/Content/bootstrap.min.css?v=20151104105858" rel="stylesheet" type="text/css" />
<script src="/Scripts/bootstrap.min.js?v=20151029213517"></script>

Si vous utilisez la classe d'extension, vous voudrez peut-être ajouter le traitement des erreurs au cas où l'appel MapPath ne fonctionnerait pas, puisque contentPath n'est pas un fichier physique.

0
Uwe Keim

Le principal problème avec cette méthode est principalement que vous devez vous rappeler de mettre à jour votre numéro de version dans votre code chaque fois que vous apportez une modification à vos fichiers css ou js.

Une meilleure façon de le faire est de définir un paramètre unique garanti avec chacun de vos fichiers css ou js, comme suit:

<script src="scripts/myjavascript.js?_=<%=DateTime.Now.Ticks%>" type="text/javascript"></script>
<link href="styles/mystyle.css?_=<%=DateTime.Now.Ticks%>" rel="stylesheet" type="text/css" />

Cela oblige les fichiers à être demandés au serveur à chaque fois, ce qui signifie également que votre site ne sera pas aussi performant au chargement de la page, car ces fichiers ne seront jamais mis en cache et utiliseront à chaque fois une bande passante inutile.

En gros, si vous vous souvenez de mettre à jour le numéro de version à chaque modification, vous pouvez vous en tirer.

0
Tim S. Van Haren

J'utilise une manière similaire de faire la même chose que vous faites sans modifier chaque page. Un événement PreRender ajouté est un fichier maître. Il garde ma logique à un endroit et applicable aux fichiers js et css.

protected void Page_PreRender(object sender, EventArgs e)
    {
        HtmlLink link = null;
        LiteralControl script = null;


        foreach (Control c in Header.Controls)
        {
            //StyleSheet add version
            if (c is HtmlLink)
            {
                link = c as HtmlLink;


                if (link.Href.EndsWith(".css", StringComparison.InvariantCultureIgnoreCase))
                {
                    link.Href += string.Format("?v={0}", ConfigurationManager.AppSettings["agVersion"]);
                }

            }

            //Js add version
            if (c is LiteralControl)
            {
                script = c as LiteralControl;

                if (script.Text.Contains(".js"))
                {
                    var foundIndexes = new List<int>();


                    for (int i = script.Text.IndexOf(".js\""); i > -1; i = script.Text.IndexOf(".js\"", i + 1))
                    {

                        foundIndexes.Add(i);
                    }

                    for (int i = foundIndexes.Count - 1; i >= 0; i--)
                    {

                        script.Text = script.Text.Insert(foundIndexes[i] + 3, string.Format("?v={0}", ConfigurationManager.AppSettings["agVersion"]));
                    }
                }

            }

        }
    }
0
Jay