ASP.NET MVC中的自动版本控制对于CSS / JS文件有什么作用?

19

我已经阅读了很多有关如何自动版本控制CSS/JS文件的文章,但这些文章中没有一篇提供了在ASP.NET MVC中优雅地实现此功能的方法。

这个链接 - 如何强制浏览器重新加载缓存的CSS/JS文件? - 提供了Apache的解决方案,但我有点糊涂如何通过ASP.NET MVC实现这一点?

是否有人能够提供一些建议,如何在IIS7和ASP.NET MVC上进行操作,以便于在URL中自动插入版本号,而不改变文件位置?就像这样的链接等,可能使用URL Rewrite或其他方式实现?

<link rel="stylesheet" href="/css/structure.1194900443.css" type="text/css" />
<script type="text/javascript" src="/scripts/prototype.1197993206.js"></script>

谢谢

4个回答

16
当面临这个问题时,我编写了一系列包装函数来包装UrlHelperContent方法:

编辑:

根据下面评论中的讨论,我更新了这段代码:

public static class UrlHelperExtensions
{
    private readonly static string _version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();

    private static string GetAssetsRoot()
    {
        string root = ConfigurationManager.AppSettings["AssetsRoot"];
        return root.IsNullOrEmpty() ? "~" : root;
    }

    public static string Image(this UrlHelper helper, string fileName)
    {
        return helper.Content(string.Format("{0}/v{2}/assets/img/{1}", GetAssetsRoot(), fileName, _version));
    }

    public static string Asset(this UrlHelper helper, string fileName)
    {
        return helper.Content(string.Format("{0}/v{2}/assets/{1}", GetAssetsRoot(), fileName, _version));
    }

    public static string Stylesheet(this UrlHelper helper, string fileName)
    {
        return helper.Content(string.Format("{0}/v{2}/assets/css/{1}", GetAssetsRoot(), fileName, _version));
    }

    public static string Script(this UrlHelper helper, string fileName)
    {
        return helper.Content(string.Format("{0}/v{2}/assets/js/{1}", GetAssetsRoot(), fileName, _version));
    }
}

使用这些函数并结合以下的 rewrite 规则应该可以工作:

<rewrite>
  <rules>
    <rule name="Rewrite assets">
      <match url="^v(.*?)/assets/(.*?)" />
      <action type="Rewrite" url="/assets/{R:2}" />
    </rule>
  </rules>
</rewrite>

这篇文章讨论了如何在IIS7上创建重写规则。

这段代码将当前程序集的版本号作为查询字符串参数添加到所发出的文件路径中。当我对网站进行更新并增加生成编号时,文件的查询字符串参数也会相应增加,这样用户代理就会重新下载该文件。


嘿,clonked - 感谢您提供的代码,但使用版本号会导致上述文章中详细描述的问题。这将防止缓存,因为带有GET参数的请求可能无法被缓存(根据HTTP规范),并且IE以无视版本号而闻名 - 这就是我想要在URL /file.123.css中包含版本号的原因,即URL重写。 - Tom
非常抱歉,我没有阅读你链接的内容。但我很高兴参与了,因为我学到了我的方法是错误的!我会尝试修改我的解决方案并分享它。 - Nathan Anderson
我已经更新了我发布的代码,使用了IIS 7的URL重写功能。 - Nathan Anderson
哦,太酷了,非常感谢 :) 所以你是通过http://weblogs.asp.net/scottgu/archive/2010/04/20/tip-trick-fix-common-seo-problems-using-the-url-rewrite-extension.aspx使用重写工具包的吗? - Tom
是的,我发布的重写规则应该可以与微软的IIS7重写模块一起使用。 - Nathan Anderson
这个对我来说并没有完全起作用,直到我把匹配改成了"<match url="^v(.*?)/Content/(.*)$" />" - 这花费了我很长时间才找到!也许这条评论会为其他人节省一些时间。除此之外,它是完美的,谢谢。 - ETFairfax

1

更新:之前的版本在Azure上无法正常工作,我已经进行了简化和更正。 (请注意,在使用IIS Express的开发模式下,您需要从Microsoft http://www.iis.net/downloads/microsoft/url-rewrite 安装URL Rewrite 2.0 - 它使用WebPi安装程序,请确保先关闭Visual Studio)

更新:修复了.min文件的规则

最近,我花费了一整天时间尝试让C# / Net 4.6 / MVC 5 / Razor中的自动捆绑(以支持自动版本控制)正常工作。 我阅读了许多关于StackOverflow和其他地方的文章,但我找不到一个端到端的指南来说明如何设置它。 我也不喜欢文件的版本方式(通过将版本查询字符串附加到静态文件请求中 - 即somefile.js?v=1234),因为有些人告诉我某些代理服务器在缓存静态资源时会忽略查询字符串。

因此,在短暂的探索之后,我已经为自动版本控制编写了自己的版本,并包含了完整的说明以使其正常工作。

完整讨论请参考:ASP.NET MVC 5中的简化自动版本控制以解决缓存问题(可在Azure和本地使用,不管是否启用URL Rewrite)

问题:通常在一个项目中会有两种类型的javascript/css文件。

1) 第三方库文件(如jquery或mustache),这些文件很少更改(当更改时,文件的版本通常会更改) - 这些文件可以使用WebGrease或JSCompress.com进行“按需”捆绑/压缩(只需在_Layout.cshtml中包含捆绑文件/版本即可)

2) 页面特定的css/js文件应在新版本构建推送时刷新。(无需用户清除其缓存或进行多次刷新)

我的解决方案是:每次构建项目时自动递增程序集版本,并使用该数字为特定资源上的路由静态文件,以保持其更新(因此将something.js包含为something.v1234.js,其中1234在每次构建项目时自动更改)。我还添加了一些额外的功能,以确保在生产中使用.min.js文件,在调试时使用regular.js文件(我正在使用WebGrease自动化缩小过程)。这个解决方案的一个好处是它可以在本地/开发模式和生产模式下工作。如何实现:每次构建项目时自动递增程序集版本,并使用该数字为特定资源上的路由静态文件,以保持其更新(因此将something.js包含为something.v1234.js,其中1234在每次构建项目时自动更改)。我还添加了一些额外的功能,以确保在生产中使用.min.js文件,在调试时使用regular.js文件(我正在使用WebGrease自动化缩小过程)。这个解决方案的一个好处是它可以在本地/开发模式和生产模式下工作。(我使用的是Visual Studio 2015/Net 4.6,但我相信这也适用于早期版本。)

步骤1: 在编译时启用程序集的自动递增编号 在AssemblyInfo.cs文件中(在项目的“属性”部分找到),更改以下行:

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

to

[assembly: AssemblyVersion("1.0.*")]
//[assembly: AssemblyFileVersion("1.0.0.0")]

步骤2:在web.config中为具有嵌入版本slug的文件设置url重写(请参见步骤3)
在web.config(项目的主要配置文件)中,在system.webServer中添加以下规则。
<rewrite>
  <rules>
    <rule name="static-autoversion">
      <match url="^(.*)([.]v[0-9]+)([.](js|css))$" />
      <action type="Rewrite" url="{R:1}{R:3}" />
    </rule>
    <rule name="static-autoversion-min">
      <match url="^(.*)([.]v[0-9]+)([.]min[.](js|css))$" />
      <action type="Rewrite" url="{R:1}{R:3}" />
    </rule>
  </rules>
</rewrite>

步骤三:设置应用程序变量以读取当前程序集版本并在js和css文件中创建版本标识。

在Global.asax.cs文件中(位于项目根目录中),在protected void Application_Start()函数中添加以下代码(在注册行之后)。

            // setup application variables to write versions in razor (including .min extension when not debugging)
            string addMin = ".min";
            if (System.Diagnostics.Debugger.IsAttached) { addMin = ""; }  // don't use minified files when executing locally
            Application["JSVer"] = "v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString().Replace('.','0') + addMin + ".js";
            Application["CSSVer"] = "v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString().Replace('.', '0') + addMin + ".css";

步骤 4:使用我们在 Global.asax.cs 中设置的应用程序变量更改 Razor 视图中的 src 链接。

@HttpContext.Current.Application["CSSVer"]
@HttpContext.Current.Application["JSVer"]

例如,我的_Layout.cshtml文件中,在头部区域,我有以下的代码块来引入样式表:
<!-- Load all stylesheets -->
<link rel='stylesheet' href='https://fontastic.s3.amazonaws.com/8NNKTYdfdJLQS3D4kHqhLT/icons.css' />
<link rel='stylesheet' href='/Content/css/main-small.@HttpContext.Current.Application["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/medium.@HttpContext.Current.Application["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/large.@HttpContext.Current.Application["CSSVer"]' />
@RenderSection("PageCSS", required: false)

这里有几个需要注意的地方:1)文件没有扩展名。2)也没有.min扩展名。这两个都由Global.asax.cs中的代码处理。
同样,在我的javascript部分(也在_Layout.cs中),我有以下代码:
<script src="~/Scripts/all3bnd100.min.js" type="text/javascript"></script>
<script src="~/Scripts/ui.@HttpContext.Current.Application["JSVer"]" type="text/javascript"></script>
@RenderSection("scripts", required: false)

第一个文件是我手动使用WebGrease创建的所有第三方库的捆绑包。如果我添加或更改了捆绑包中的任何文件(这很少见),那么我会手动将文件重命名为all3bnd101.min.js、all3bnd102.min.js等等...此文件与重写处理程序不匹配,因此将保留在客户端浏览器上缓存,直到您手动重新捆绑/更改名称。
第二个文件是ui.js(根据您是否在调试模式下运行,将编写为ui.v12345123.js或ui.v12345123.min.js)。这将被处理/重写。(您可以在Global.asax.cs的Application_OnBeginRequest中设置断点以查看它的工作情况)
有关此问题的完整讨论,请参见: ASP.NET MVC 5中简化Javascript / CSS自动版本控制以解决缓存问题的方法(可在Azure和本地使用)(包括一种无需URL重写的方法)

-1

我通常会向我的资源文件附加一个虚假的查询字符串.. 例如

<link rel="stylesheet" href="/css/structure.css?v=1194900443" type="text/css" />
<script type="text/javascript" src="/scripts/prototype.js?v=1197993206"></script>

它不需要任何url助手,并且无论后台运行什么都可以工作。老实说,我还没有彻底测试过这种方法,但我发现它总是可以解决人们遇到的任何资源缓存问题。

您可能需要手动更新v = ,但从某个配置文件中附加版本参数到资源上并不难。

编辑:

我回去仔细阅读了上面链接的内容,意识到您可能已经放弃了这种方法。对于再次建议此方法表示歉意。


当我在Firefox和Chrome中测试时,这完全阻止了缓存。 - Sam

-1

我认为下一个解决方案是使用高级选项(调试/发布模式,版本):

通过这种方式包含Js或Css文件:

<script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
<link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />

Global.JsPostfix和Global.CssPostfix是在Global.asax中通过以下方式计算的:

protected void Application_Start(object sender, EventArgs e)
{
    ...
    string jsVersion = ConfigurationManager.AppSettings["JsVersion"];
    bool updateEveryAppStart = Convert.ToBoolean(ConfigurationManager.AppSettings["UpdateJsEveryAppStart"]);
    int buildNumber = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Revision;
    JsPostfix = "";
#if !DEBUG
    JsPostfix += ".min";
#endif      
    JsPostfix += ".js?" + jsVersion + "_" + buildNumber;
    if (updateEveryAppStart)
    {
        Random rand = new Random();
        JsPosfix += "_" + rand.Next();
    }
    ...
}

我认为你不能在带有runat="server"属性的head标签中添加<%=Global.JsPostfix%>这种服务器代码。 - HasanG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接