强制浏览器在asp.net应用程序中获取最新的js和css文件


104

某些浏览器会缓存js和css文件,除非您强制将它们刷新,否则无法刷新它们。最简单的方法是什么。

我刚刚实施了似乎可行的解决方案。

在页面上声明一个版本变量

  public string version { get; set; }

从web.config密钥获取版本号

 version = ConfigurationManager.AppSettings["versionNumber"];

在您的aspx页面中,像这样调用javascript和样式表

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

因此,如果您在web.config中将1.0的版本设置为1.1,则浏览器将下载最新文件,这有望为您和您的用户省去一些麻烦。

是否有另一种更好的解决方案,还是会导致网站出现无法预料的问题?


有趣的问题是,我最近也遇到过同样的问题,但是这只是开发测试期间的问题。不太在乎它,因为我们不打算在启动后更改那些文件。很想知道一个解决方案,以供将来参考!
Brett Allen

我看到的唯一问题是,对web.config的更改将在后台调用应用程序重新启动:msdn.microsoft.com/en-us/library/aa478432.aspx
monty 2013年

感谢你的提问。这帮助我解决了一个大问题。
雷迪

Answers:


76

我通过添加最后修改的时间戳作为脚本的查询参数来解决此问题。

我使用扩展方法进行了此操作,并在CSHTML文件中使用了它。 注意:此实现将时间戳缓存1分钟,因此我们不会对磁盘​​造成太大的影响。

这是扩展方法:

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;
        }
    }
}

然后在CSHTML页面中:

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

在呈现的HTML中,这显示为:

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

1
这对mvc非常有用,我想知道最新的mvc 5框架是否可以解决此问题?捆绑与-{version}通配符一起使用的方法也是解决此问题的一种方法,但是它要求在每次创建新文件后重命名文件...
kiev 2014年

我在一个Webforms网站中使用了您的MVC示例的基础知识,并且效果很好,感谢您的分享!
布赖恩2015年

是否应该担心显示资源文件的最后修改日期/时间戳?如果正在使用的文件具有几年前的日期/时间戳记,是否可能是公司可能不希望对其用户公开的信息?
布赖恩

这是标准方式,大多数情况下都遵循应用程序。但是,只有在部署或构建应用程序时,时间戳记才应更改。否则,每次用户刷新页面或切换到应用程序中的其他页面时。浏览器将再次下载所有样式表和JavaScript,这不好
塔伦(Tarun)2015年

2
对页面性能有什么影响?加载页面会导致多少延迟?
Durgesh Sonawane

28

您的解决方案有效。实际上,它非常受欢迎。

甚至堆栈溢出也使用类似的方法:

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

v=6184SVN版本号可能在哪里。


与公认的答案中描述的方法相比,这是一种更加费力的方法。每次提供页面时检查文件的SVN版本都是性能开销,尤其是随着用户数量的增加而增加时。
Neolisk '16

4
您可以在构建期间获取修订号,将其写入文件(例如,部分.cs文件),并将其包含在项目中,因此您无需在运行时从svn读取它。我已将这种方法与msbuild一起使用,以将修订版本号放入svn的AssemblyInfo.cs文件中。
拉马赞·比纳尔巴西

2
使用全局版本号/修订号至少有一个缺点:发布网站更新会使所有.js和.css文件(而不只是更改的文件)的浏览器缓存无效。在大多数应用程序中,这可能无关紧要,但出于完整性考虑,我将其提及。
亚当·特根

如果您在部署或构建过程中自动更新assemblyinfo.cs文件的版本,则次要版本可用于该编号。
kristianp '18

28

ASP.NET Core(MVC 6)中,这可以通过asp-append-version标记帮助器立即使用:

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

1
感谢您让我们知道!我以前不知道!
Federico Navarrete'7

18

如果您对JS / CSS使用捆绑软件,则ASP.NET MVC将为您解决这一问题。它将以GUID的形式自动将版本号附加到您的捆绑软件中,并且仅在捆绑软件更新时更新该GUID(aka任何源文件都有更改)。

如果您有大量的JS / CSS文件,这也会有所帮助,因为它可以大大缩短内容加载时间!

看这里


您的意思是说,如果我们在MVC应用程序中使用捆绑软件,那么这里不需要发布答案中的任何方法吗?如果是这样的话,我曾经想过,捆绑确实更为重要。您能否向我们澄清这些问题?谢谢。
杰克

1
对,就是这样。只要脚本包含在捆绑软件中,当在捆绑软件的任何源文件中检测到更改时,它将为每个捆绑软件自动生成版本号。
jonesy827

并且不要忘记,作为开发人员您仍然会头疼。ASP.NET捆绑在调试和开发过程中无济于事。
it3xl19年

12

在asp.net中有一个内置方法:bundled。只需使用它。每个新版本都有唯一的后缀“?v = XXXXXXX”。在调试模式下,捆绑处于关闭状态,以便在web.config中打开make设置:

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

或添加到方法RegisterBundles(BundleCollection bundles):

BundleTable.EnableOptimizations = true;

例如:

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")

但这仅适用于发布或生产环境。调试模式打开时如何进行开发?捆绑包仍然可以解决此问题吗?
VAAA

是的,bundlind并不能使开发人员的生活更轻松。每次更改脚本后都必须按Ctrl-F5。而且,如果您有相框,它会变得更加有趣。
it3xl19年

6

这个问题的答案比操作中操作员给出的答案更简单(方法相同):

在web.config中定义密钥:

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

直接从aspx页面调用appsettings:

<link href="styles/navigation.css?v=<%=ConfigurationManager.AppSettings["VersionNumber"]%>" rel="stylesheet" type="text/css" />

我喜欢这个,但担心为什么这个解决方案的投票如此之少……
SimplyInk

@SimplyInk我不知道,但是有20种不同的答案,因此可能与它有关。如果它可以工作并且您喜欢它,请随时对其进行投票。
JackArbiter

4

基于Adam Tegan的答案,进行了修改以用于Web窗体应用程序。

在.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);
    }
}

在aspx标记中:

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

在呈现的HTML中,它显示为

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

2
嘿! 您的示例正在运行,但是您应该删除缓存引用或修复代码以使用缓存,因为它可能会混淆您使用它的原因。要使用缓存,在context.Cache [filename] == null的情况下,应使用context.Cache.Add方法将文件的版本添加到缓存中。如果context.Cache [filename]!= null,那么您应该返回缓存的值(context.Cache [filename])
Flavia Obreja 2015年

1
Flavia,我认为您的解释是有道理的,并且我认为这是一种更简单,更有效的实现。感谢您发布有用的评论和反馈。
Bryan 2015年

4

有趣的是,即使它应该是故障安全的,该站点也存在与您描述的一些代理设置有关的方法问题。

查看此元堆栈溢出讨论。

因此,鉴于此,可能不使用GET参数进行更新,而是使用实际的文件名:

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

即使要做更多的工作,您也必须实际创建文件或为其构建URL重写。


4

我想要一个简单的衬板,使该路径成为破坏缓存的唯一路径。这对我有用:

<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>

如果文件自上次加载到页面以来已被修改,浏览器将提取更新的文件。

last modified.js文件生成戳记,然后将其夹在其中,而不是可能不容易获得访问的版本。

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

另一种选择是获取文件的校验和。


1
到目前为止,最简单的方法,而且可能是最低的开销。
Tony Hinkle

1
完美的解决方案。我还测试了Chrome 70,Firefox 63和IE 11,以确保缓存确实有效。它是。这只会破坏在文件的新版本上缓存,至少在最新版本的浏览器上。我在其他地方听说过,有些浏览器会使用查询字符串(?)重新加载每个文件。也许以前是这样,或者在Safari和Opera中仍然如此。DK。
布拉德·马修斯

3

这是与ASP.NET 5 / MVC 6 / vNext一起使用的方法

步骤1:创建一个类,以返回文件的上次写入时间,类似于该线程中的其他答案。请注意,这需要ASP.NET 5(或其他)依赖项注入。

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;
     }
}

步骤2:将要注入的服务注册到startup.cs中

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

步骤3:然后,在ASP.NET 5中,可以将服务直接注入到诸如_Layout.cshtml之类的布局视图中,如下所示:

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

可以完成一些整理工作,以更好地组合物理路径并以与语法更一致的样式来处理文件名,但这是一个起点。希望它能帮助人们迁移到ASP.NET 5。


此行为实际上是开箱即用的,请参阅我的答案
metalheart

3

我在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;
    }
}

然后在实际视图中:

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

3

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

这适用于所有浏览器。在这里,我使用PHP生成随机数。您可以使用自己的服务器端语言。


好的答案,但是如果您不考虑亚当解释的内容,ASP MVC可能会有点问题,因为我尝试过它,并且如果您使用MVC 5,则Bundle文件夹无法识别它。但是感谢您的建议!
Federico Navarrete

2

上面的答案开始,我修改了一些代码以使帮助程序也能与CSS文件一起使用,并且每次在文件中进行某些更改时(不仅是在构建时)都添加一个版本

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;
        }
    }
}

1

获取文件修改时间,如下图

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

将此附加为查询字符串

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

从标记中调用它。

MVC扩展助手方法

添加扩展方法

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

    }
}

在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>

在视图中使用它

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

1

简化的先前建议,并为.NET Web Forms开发人员提供了代码。

这将在资源的文件路径中接受相对(“〜/”)和绝对URL。

放入一个静态扩展类文件,如下:

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];
}

然后像这样在母版页中调用它:

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

也是不错的方法!
Federico Navarrete'7

0

基于以上答案,我编写了一个小的扩展类来处理CSS和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;
        }
    }
}

而不是这样写:

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

您现在可以编写:

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

即简单地替换Url.ContentUrl.VersionedContent

生成的URL类似于:

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

如果使用扩展类,则可能要添加错误处理,以防MapPath调用不起作用,因为contentPath它不是物理文件。


0

我使用类似的方法来做您正在做的事情,而无需修改每个页面。添加了一个PreRender事件是主文件。它把我的逻辑放在一个地方,并且适用于js和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

您可以覆盖脚本或样式的DefaultTagFormat属性。

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


0

通过以下概念在.net应用程序中实现CSS版本控制的简便方法:无需编写后端代码。

<link href="<%="../../App_Themes/Base/css/main.css?v="+ DateTime.Now.ToString("yyyyMMddhhmmss") +""%>" rel="stylesheet" />

即使文件完全没有更改,此操作也会在每个页面呈现中进行下载。
Thanasis Ioannidis

@ThanasisIoannidis它可以使用定期更改文件的位置。另一个选项是在web.config中添加appVersion密钥,并与文件名..一起使用,但是在发布用于prod的应用程序时需要更新。
SantoshK

-1

这样做的主要问题主要是,每次对CSS或JS文件进行任何更改时,您都需要记住要更新代码中的版本号。

可能更好的方法是为每个css或js文件设置一个保证唯一的参数,如下所示:

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

这会强制每次都从服务器请求文件,这也意味着您的站点在页面加载时不会表现出色,因为这些文件将永远不会被缓存,并且每次都会使用不需要的带宽。

本质上,如果您记得每次进行更改时都会更新版本号,那么您就可以摆脱它的工作方式。


9
并且也使用大量带宽。
Darren Kopp

2
正确,您不希望在每次页面加载时都使用新版本的JS ...您只希望浏览器每次实际拥有更新版本时都寻找新版本。
kingdango 2014年

对于正在开发中的50KB css文件上的临时解决方案,这是完全可以接受的。+1
Colbs

-2

对于ASP.NET页面,我正在使用以下内容

之前

<script src="/Scripts/pages/common.js" type="text/javascript"></script>

之后(强制装弹)

 <script src="/Scripts/pages/common.js?ver<%=DateTime.Now.Ticks.ToString()%>" type="text/javascript"></script>

添加DateTime.Now.Ticks效果很好。


是的,问题出在带宽上-例如在stackoverflow.com/a/2185918/59508
kiev 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.