ASP.NET MVC 中的 CSS/JS 自动版本控制?

发布于 2024-11-04 11:35:48 字数 668 浏览 3 评论 0原文

所以我正在阅读这篇 stackoverflow 帖子 ASP.NET MVC 中针对 CSS/JS 文件的“自动版本控制”,并且想知道执行此操作的“最佳”策略是什么。

提供的解决方案会插入一个程序集编号 - 这意味着每次发布时 - 它都会更改每个文件,这不理想,因为如果您仅对 1 *.css 或 *.js 进行修改,那么它就会更改每个文件。

1)如何仅针对“单个文件”而不是使用修改日期或 IIS7 上的其他内容来使用站点范围的程序集?

2)此外,如果我有某种“静态”资产,例如 - http://static.domain。 com/js/123.js - 如果有人将此静态链接集成到他们的网站上,我如何使用重写来发送请求的最新文件?

http://static.domain.com/js/123.js 是链接当收到此请求时 - 检查并发送最新文件?

So I was reading this stackoverflow post about "autoversioning" in ASP.NET MVC for CSS/JS files and was wondering what the "best" strategy is to do this.

The solution provided inserts an assembly number - which means everytime you publish - it will change EVERY SINGLE file which is not ideal because if you make modifications to just 1 *.css or *.js then it will change each and every file.

1) How can it be done just for "single files" instead of using site wide assembly using modification date or something on IIS7 ?

2) Also if I have some sort of "static" asset like - http://static.domain.com/js/123.js - how can I use rewrite to send the latest file for a request if someone has integrated this static link onto their site ?

i.e. http://static.domain.com/js/123.js is the link and when a request comes for this - check and send latest file ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

臻嫒无言 2024-11-11 11:35:48

ASP.NET 4.5+ 附带了 内置捆绑&缩小框架
旨在解决这个问题。

如果您绝对需要一个简单的自行解决方案,您可以使用下面的答案,但我总是说正确的方法是使用捆绑和解决方案。缩小框架。


您可以像这样修改 AssemblyInfo.cs 文件:

Change
[assembly: AssemblyVersion("1.0.0.0")]
to    
[assembly: AssemblyVersion("1.0.*")]

这意味着每次构建项目时,它都会有一个比前一个版本更高的新程序集版本。现在您有了唯一的版本号。

创建一个 UrlHelperExtension 类,该类将帮助在视图中需要时获取此信息:

public static class UrlHelperExtensions
{
    public static string ContentVersioned(this UrlHelper self, string contentPath)
    {
        string versionedContentPath = contentPath + "?v=" + Assembly.GetAssembly(typeof(UrlHelperExtensions)).GetName().Version.ToString();
        return self.Content(versionedContentPath);
    }
}

您现在可以通过以下方式轻松地将版本号添加到视图中:

<link href="@Url.ContentVersioned("style.css")" rel="stylesheet" type="text/css" />

当查看页面源时,您可以现在会有类似的东西

<link href="style.css?v=1.0.4809.30029" rel="stylesheet" type="text/css" />

ASP.NET 4.5+ comes with a built-in bundling & minification framework
which is designed to solve this problem.

If you absolutely need a simple roll-your-own solution you can use the answer below, but I would always say the correct way is to use a bundling & minification framework.


You can modify the AssemblyInfo.cs file like so:

Change
[assembly: AssemblyVersion("1.0.0.0")]
to    
[assembly: AssemblyVersion("1.0.*")]

This means that every time the project is built, it will have a new assembly version which is higher than the previous one. Now you have your unique version number.

Create an UrlHelperExtension class that will help get this information when needed in the views:

public static class UrlHelperExtensions
{
    public static string ContentVersioned(this UrlHelper self, string contentPath)
    {
        string versionedContentPath = contentPath + "?v=" + Assembly.GetAssembly(typeof(UrlHelperExtensions)).GetName().Version.ToString();
        return self.Content(versionedContentPath);
    }
}

You can now easily add a version number to your views in the following manner:

<link href="@Url.ContentVersioned("style.css")" rel="stylesheet" type="text/css" />

When viewing your page source you will now have something that looks like

<link href="style.css?v=1.0.4809.30029" rel="stylesheet" type="text/css" />
浊酒尽余欢 2024-11-11 11:35:48

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

如果您想更改实际名称文件,而不是附加查询字符串(静态文件的某些代理/浏览器会忽略它)您可以按照以下步骤操作:(我知道这是一篇旧帖子,但我在开发解决方案时遇到了它:

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

第 1 步:在构建时在程序集上启用自动增量
在 AssemblyInfo.cs 文件(位于项目的“属性”部分下)中,将以下行:更改

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

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

步骤 2: 在 web.config 中为具有嵌入版本别名的文件设置 url 重写(请参阅步骤3)

在 web.config (项目的主要配置)中,在 部分添加以下规则,我将其直接放在 结束标记

<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 文件中创建版本别名。

第 3 步: 设置应用程序变量以读取当前的程序集版本并在 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 步: 使用我们设置的应用程序变量更改 Razor 视图中的 src 链接 在 Global.asax.cs 中

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

例如,

<!-- Load all stylesheets -->
<link rel='stylesheet' href='https://fontastic.s3.amazonaws.com/8NNKTYdfdJLQS3D4kHqhLT/icons.css' />
<link rel='stylesheet' href='/Content/css/[email protected]["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/[email protected]["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/[email protected]["CSSVer"]' />
@RenderSection("PageCSS", required: false)

,在我的 _Layout.cshtml 的 head 部分中,我有以下样式表代码块:这里需要注意几件事:1) 没有扩展名在文件上。 2)也没有.min。这两个文件都是由 Global.asax.cs 中的代码处理的

。同样,(也在 _Layout.cs 中)在我的 javascript 部分:我有以下代码:

<script src="~/Scripts/all3bnd100.min.js" type="text/javascript"></script>
<script src="~/Scripts/[email protected]["JSVer"]" type="text/javascript"></script>
@RenderSection("scripts", required: false)

第一个文件是我创建的所有 3rd 方库的捆绑包手动使用 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 的自动版本控制以停止缓存有或没有 URL 重写的问题(在 Azure 和本地工作) (包括不使用 URL 重写的方法)

UPDATE: The previous version did not work on Azure, I have simplified and corrected below. (Note, for this to work in development mode with IIS Express, you will need to install URL Rewrite 2.0 from Microsoft http://www.iis.net/downloads/microsoft/url-rewrite - it uses the WebPi installer, make sure to close Visual Studio first)

If you would like to change the actual names of the files, rather than appending a querystring (which is ignored by some proxies / browsers for static files) You can follow the following steps: (I know this is an old post, but I ran across it while developing a solution:

How to do it: Auto-increment the assembly version every time the project is built, and use that number for a routed static file on the specific resources you would like to keep refreshed. (so something.js is included as something.v1234.js with 1234 automatically changing every time the project is built) - I also added some additional functionality to ensure that .min.js files are used in production and regular.js files are used when debugging (I am using WebGrease to automate the minify process) One nice thing about this solution is that it works in local / dev mode as well as production. (I am using Visual Studio 2015 / Net 4.6, but I believe this will work in earlier versions as well.

Step 1: Enable auto-increment on the assembly when built
In the AssemblyInfo.cs file (found under the "properties" section of your project change the following lines:

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

to

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

Step 2: Set up url rewrite in web.config for files with embedded version slugs (see step 3)

In web.config (the main one for the project) add the following rules in the <system.webServer> section I put it directly after the </httpProtocol> end tag.

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

Step 3: Setup Application Variables to read your current assembly version and create version slugs in your js and css files.

in Global.asax.cs (found in the root of the project) add the following code to protected void Application_Start() (after the Register lines)

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

Step 4: Change src links in Razor views using the application variables we set up in Global.asax.cs

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

For example, in my _Layout.cshtml, in my head section, I have the following block of code for stylesheets:

<!-- Load all stylesheets -->
<link rel='stylesheet' href='https://fontastic.s3.amazonaws.com/8NNKTYdfdJLQS3D4kHqhLT/icons.css' />
<link rel='stylesheet' href='/Content/css/[email protected]["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/[email protected]["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/[email protected]["CSSVer"]' />
@RenderSection("PageCSS", required: false)

A couple things to notice here: 1) there is no extension on the file. 2) there is no .min either. Both of these are handled by the code in Global.asax.cs

Likewise, (also in _Layout.cs) in my javascript section: I have the following code:

<script src="~/Scripts/all3bnd100.min.js" type="text/javascript"></script>
<script src="~/Scripts/[email protected]["JSVer"]" type="text/javascript"></script>
@RenderSection("scripts", required: false)

The first file is a bundle of all my 3rd party libraries I've created manually with WebGrease. If I add or change any of the files in the bundle (which is rare) then I manually rename the file to all3bnd101.min.js, all3bnd102.min.js, etc... This file does not match the rewrite handler, so will remain cached on the client browser until you manually re-bundle / change the name.

The second file is ui.js (which will be written as ui.v12345123.js or ui.v12345123.min.js depending on if you are running in debug mode or not) This will be handled / rewritten. (you can set a breakpoint in Application_OnBeginRequest of Global.asax.cs to watch it work)

Full discussion on this at: Simplified Auto-Versioning of Javascript / CSS in ASP.NET MVC 5 to stop caching issues (works in Azure and Locally) With or Without URL Rewrite (including a way to do it WITHOUT URL Rewrite)

有深☉意 2024-11-11 11:35:48

1)
请改用文件修改时间。这是一个示例:

public static string GeneratePathWithTime(string cssFileName)
{
  var serverFilePath = server.MapPath("~/static/" + cssFileName);
  var version = File.GetLastWriteTime(serverFilePath).ToString("yyyyMMddhhmmss");
  return string.Format("/static/{0}/{1}", version, cssFileName);
}

这将为“style.css”生成类似“/static/201109231100/style.css”的路径(假设您的 style.css 位于 static 目录中)。
然后,您将在 IIS 中添加重写规则,将“/static/201109231100/style.css”重写为“/static/style.css”。版本号只有在css文件被修改时才会改变,并且仅适用于修改过的文件。

2)
您可以通过 HttpModule 处理对 123.js 的请求并发送其最新内容,但我认为您不能保证请求获得最新版本。这取决于浏览器如何处理其缓存。您可以在响应标头中设置较早的过期时间(例如一分钟前),以告诉浏览器始终重新下载文件,但这完全取决于浏览器本身来决定是否重新下载文件。这就是为什么我们每次更新问题 1) 中的文件时都需要为修改后的文件生成不同的路径,如果以前从未访问过该 URL,则浏览器将始终尝试下载该文件。

1)
Use file modification time instead. Here's an example:

public static string GeneratePathWithTime(string cssFileName)
{
  var serverFilePath = server.MapPath("~/static/" + cssFileName);
  var version = File.GetLastWriteTime(serverFilePath).ToString("yyyyMMddhhmmss");
  return string.Format("/static/{0}/{1}", version, cssFileName);
}

This will generate a path like "/static/201109231100/style.css" for "style.css" (assuming the your style.css is located in the static directory).
You'll then add a rewrite rule in IIS to rewrite "/static/201109231100/style.css" to "/static/style.css". The version number will only be changed when the css file has been modified and only applies to modified files.

2)
You can handle the request to 123.js via an HttpModule and send the latest content of it, but I don't think you can guarantee the request gets the latest version. It depends on how the browser handles its cache. You can set an earlier expiration time (for example, one minute ago) in your response header to tell the browsers to always re-download the file, but it's all up to the browser itself to decide whether to re-download the file or not. That's why we need to generate a different path for our modified files each time we updated our files in your question 1), the browser will always try to download the file if the URL has never been visited before.

深海夜未眠 2024-11-11 11:35:48

我编写了一个 Url Helper 来为我执行 CacheBusting。

public static string CacheBustedContent(this UrlHelper helper, string contentPath)
{
    var path = string.Empty;

    if (helper.RequestContext.HttpContext.Cache["static-resource-" + contentPath] == null)
    {
        var fullpath = helper.RequestContext.HttpContext.Server.MapPath(contentPath);
        var md5 = GetMD5HashFromFile(fullpath);
        path = helper.Content(contentPath) + "?v=" + md5;

        helper.RequestContext.HttpContext.Cache.Add("static-resource-" + contentPath, path, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(24, 0, 0), System.Web.Caching.CacheItemPriority.Default, null);
    }
    else
    {
        path = helper.RequestContext.HttpContext.Cache["static-resource-" + contentPath].ToString();
    }

    return path;
}

您可以将 GetMD5HashFromFile() 替换为 CRC 或任何其他类型的调用,这些调用会根据文件的内容或上次修改日期生成唯一的字符串。

缺点是每当缓存失效时就会调用它。如果您以某种方式实时更改文件,但不重置应用程序池,您可能需要触摸 web.config 才能使其正确重新加载。

I wrote a Url Helper which does the CacheBusting for me.

public static string CacheBustedContent(this UrlHelper helper, string contentPath)
{
    var path = string.Empty;

    if (helper.RequestContext.HttpContext.Cache["static-resource-" + contentPath] == null)
    {
        var fullpath = helper.RequestContext.HttpContext.Server.MapPath(contentPath);
        var md5 = GetMD5HashFromFile(fullpath);
        path = helper.Content(contentPath) + "?v=" + md5;

        helper.RequestContext.HttpContext.Cache.Add("static-resource-" + contentPath, path, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(24, 0, 0), System.Web.Caching.CacheItemPriority.Default, null);
    }
    else
    {
        path = helper.RequestContext.HttpContext.Cache["static-resource-" + contentPath].ToString();
    }

    return path;
}

You could replace the GetMD5HashFromFile() with CRC or any other sort of call which generates a unique string based on the contents or last-modified-date of the file.

The downside is this'll get called whenever the cache is invalidated. And if you change the file on live somehow, but don't reset the application pool, you'll probably need to touch the web.config to get it to reload correctly.

甜是你 2024-11-11 11:35:48

这个问题现在确实很老了,但如果有人偶然发现它,据我所知,这是当前的技术水平:

  1. 在 ASP.NET Core 中,您可以使用 TagHelpers 并只需添加 asp-append-version< /code> 属性到任何 ;

  2. 对于 ASP.NET Core 和 Framework,都有一个名为 WebOptimizer 的 NuGet 包 (https://github.com/ligershark/WebOptimizer)。它允许捆绑和缩小,并且还会将基于内容的版本字符串附加到您的文件中。

  3. 如果您想自己执行此操作,可以使用方便的 IFileVersionProvider 接口,您可以从 .NET Core 中的 IServiceProvider 获取该接口:

    // 这个例子假设,你至少有一个 HttpContext
    var fileVersionProvider = httpContext.RequestServices.GetRequiredService();
    字符串路径 = httpContext.Content("/css/site.css");
    字符串pathWithVersionString = fileVersionProvider.AddFileVersionToPath(httpContext.Request.PathBase, 路径);
    

    对于 .NET Framework,您可以从此处获取 FileVersionProvider 源:https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Razor/src/Infrastruct/DefaultFileVersionProvider.cs< /a>
    您必须做一些工作,例如用 MemoryCache.DefaultConcurrentDictionary 或其他东西替换缓存,但“肉”就在那里。


This question is really old now, but if anyone stumbles upon it, here's to my knowledge the current state of the art:

  1. In ASP.NET Core you can use TagHelpers and simply add the asp-append-version attribute to any <link> or <script> tag:

    <script src="~/js/my.js" asp-append-version="true"></script>
    
  2. For both ASP.NET Core and Framework there is a NuGet Package called WebOptimizer (https://github.com/ligershark/WebOptimizer). It allows for both bundling and minification, and will also append a content-based version string to your file.

  3. If you want to do it yourself, there is the handy IFileVersionProvider interface, which you can get from your IServiceProvider in .NET Core:

    // this example assumes, you at least have a HttpContext
    var fileVersionProvider = httpContext.RequestServices.GetRequiredService<IFileVersionProvider>();
    string path = httpContext.Content("/css/site.css");
    string pathWithVersionString = fileVersionProvider.AddFileVersionToPath(httpContext.Request.PathBase, path);
    

    For .NET Framework, you can get the FileVersionProvider source from here: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Razor/src/Infrastructure/DefaultFileVersionProvider.cs
    You will have to do some work, like replacing the Cache with MemoryCache.Default or a ConcurrentDictionary or something, but the 'meat' is there.

风尘浪孓 2024-11-11 11:35:48

您可能想看看 Dean Hume 的博客文章 MVC 和HTML5 应用程序缓存。在那篇文章中,他指出了一种自动处理每个请求版本控制的优雅方法,使用 @ShirtlessKirk:

@Url.Content("~/Content/Site.css").AppendHash(Request)

You might want to have a look at Dean Hume's Blogpost MVC and the HTML5 Application Cache. In that post, he points out an elegant way of automatically handling versioning per request, using a class library of @ShirtlessKirk:

@Url.Content("~/Content/Site.css").AppendHash(Request)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文